Существует ли лучший способ ожидать потоков с очередями?

Синтаксис, который я использую, немного отличается от предложенного @Matt:

find $directory -type f -name \*.in

(это на одно нажатие клавиши меньше).

22
задан Zanoni 2 September 2009 в 13:30
поделиться

8 ответов

Вероятно, вы захотите взглянуть на AutoResetEvent и ManualResetEvent.

Они предназначены именно для этой ситуации (ожидание завершения потока ThreadPool перед тем, как что-то сделать).

Вы бы сделали что-то вроде этого:

static void Main(string[] args)
{
    List<ManualResetEvent> resetEvents = new List<ManualResetEvent>();
    foreach (var x in Enumerable.Range(1, WORKER_COUNT))
    {
        ManualResetEvent resetEvent = new ManualResetEvent();
        ThreadPool.QueueUserWorkItem(DoSomething, resetEvent);
        resetEvents.Add(resetEvent);
    }

    // wait for all ManualResetEvents
    WaitHandle.WaitAll(resetEvents.ToArray()); // You probably want to use an array instead of a List, a list was just easier for the example :-)
}

public static void DoSomething(object data)
{
    ManualResetEvent resetEvent = data as ManualResetEvent;

    // Do something

    resetEvent.Set();
}

Edit: Забыл упомянуть, что вы можете дождаться одного потока, любого потока и так далее. Также, в зависимости от вашей ситуации, AutoResetEvent может немного упростить ситуацию, поскольку (как следует из названия) может автоматически сигнализировать о событиях: -)

14
ответ дан 29 November 2019 в 04:50
поделиться

В .NET 4.0 для этого есть новый класс Barrier .

Кроме того, ваш метод не так уж и плох, и вы можете немного оптимизировать, используя только Pulsing, если значение RunningWorkers после декремента равно 0. Это будет выглядеть так:

this.workerLocker = new object(); // Global variable
this.RunningWorkers = arrayStrings.Length; // Global variable

foreach (string someString in arrayStrings)
{
     ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
     //Thread.Sleep(100);
}

// Waiting execution for all queued threads
Monitor.Wait(this.workerLocker);

// Method DoSomething() definition
public void DoSomething(object data)
{
    // Do a slow process...
    // ...

    lock (this.workerLocker)
    {
        this.RunningWorkers--;
        if (this.RunningWorkers == 0)
           Monitor.Pulse(this.workerLocker);
    }
}

Вы можете использовать EventWaithandle или AutoResetEvent, но все они являются оболочками Win32 API. Поскольку класс Monitor представляет собой чистый управляемый код, в этой ситуации я бы предпочел Monitor.

2
ответ дан 29 November 2019 в 04:50
поделиться

Я не уверен, что есть на самом деле, я недавно сделал нечто подобное, чтобы сканировать каждый IP-адрес подсети на предмет принятия определенного порта.

Я могу предложить несколько вещей, которые могут улучшить производительность:

  • Используйте метод SetMaxThreads для ThreadPool для настройки производительности (т. Е. Балансировки одновременного выполнения большого количества потоков и времени блокировки на таком большом количестве потоков) .

  • Не спите при настройке всех рабочих элементов, в этом нет реальной необходимости (о чем я сразу понимаю. Но спите внутри метода DoSomething, возможно, только на миллисекунду, чтобы другие потоки могли подключиться к нему в случае необходимости.

Я уверен, что вы могли бы реализовать более индивидуальный метод самостоятельно, но я сомневаюсь, что он был бы более эффективным, чем использование ThreadPool.

PS Я не 100 % ясно о причине использования монитора, поскольку вы все равно блокируете? Обратите внимание, что вопрос задан только потому, что я раньше не использовал класс Monitor, а не потому, что я действительно сомневаюсь в его использовании.

0
ответ дан 29 November 2019 в 04:50
поделиться

Как насчет Fork и Join , которые используют только Monitor ;-p

Forker p = new Forker();
foreach (var obj in collection)
{
    var tmp = obj;
    p.Fork(delegate { DoSomeWork(tmp); });
}
p.Join();

Полный код показан на этом предыдущий ответ .

Или для очереди производителя / потребителя ограниченного размера (потокобезопасность и т. Д.) здесь .

13
ответ дан 29 November 2019 в 04:50
поделиться

Мне очень нравится Begin- End- Async Pattern , когда мне нужно ждать завершения задач.

Я бы посоветовал вам заключить BeginEnd в рабочий класс:

public class StringWorker
{
    private string m_someString;
    private IAsyncResult m_result;

    private Action DoSomethingDelegate;

    public StringWorker(string someString)
    {
        DoSomethingDelegate = DoSomething;
    }

    private void DoSomething()
    {
        throw new NotImplementedException();
    }

    public IAsyncResult BeginDoSomething()
    {
        if (m_result != null) { throw new InvalidOperationException(); }
        m_result = DoSomethingDelegate.BeginInvoke(null, null);
        return m_result;
    }

    public void EndDoSomething()
    {
        DoSomethingDelegate.EndInvoke(m_result);
    }
}

Для начала и работы используйте этот фрагмент кода:

List<StringWorker> workers = new List<StringWorker>();

foreach (var someString in arrayStrings)
{
    StringWorker worker = new StringWorker(someString);
    worker.BeginDoSomething();
    workers.Add(worker);
}

foreach (var worker in workers)
{
    worker.EndDoSomething();
}

Console.WriteLine("END");

Вот и все.

Примечания: Если вы хотите получить результат из BeginEnd, измените «Action» на Func и измените EndDoSomething, чтобы вернуть тип.

public class StringWorker
{
    private string m_someString;
    private IAsyncResult m_result;

    private Func<string> DoSomethingDelegate;

    public StringWorker(string someString)
    {
        DoSomethingDelegate = DoSomething;
    }

    private string DoSomething()
    {
        throw new NotImplementedException();
    }

    public IAsyncResult BeginDoSomething()
    {
        if (m_result != null) { throw new InvalidOperationException(); }
        m_result = DoSomethingDelegate.BeginInvoke(null, null);
        return m_result;
    }

    public string EndDoSomething()
    {
        return DoSomethingDelegate.EndInvoke(m_result);
    }
}
3
ответ дан 29 November 2019 в 04:50
поделиться

В дополнение к Barrier, на что указал Хенк Холтерман (кстати, это очень плохое использование Barrier, см. Мой комментарий к его ответу), .NET 4.0 предоставляет все множество других опций (чтобы использовать их в .NET 3.5, вам нужно загрузить дополнительную DLL от Microsoft ). Я написал в блоге сообщение , в котором перечислены все , но мой любимый, безусловно, Parallel.ForEach:

Parallel.ForEach<string>(arrayStrings, someString =>
{
    DoSomething(someString);
});

За кулисами Parallel.ForEach ставит очередь в новый и улучшенный пул потоков и ждет, пока все потоки не будут выполнены.

4
ответ дан 29 November 2019 в 04:50
поделиться

Используйте Spring Threading. В него встроены реализации Barrier.

http://www.springsource.org/extensions/se-threading-net

0
ответ дан 29 November 2019 в 04:50
поделиться

Yes, there is.

Suggested approach

1) a counter and a wait handle

int ActiveCount = 1; // 1 (!) is important
EventWaitHandle ewhAllDone = new EventWaitHandle(false, ResetMode.Manual);

2) adding loop

foreach (string someString in arrayStrings)
{
     Interlocked.Increment(ref ActiveCount);

     ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
     // Thread.Sleep(100); // you really need this sleep ?
}

PostActionCheck();
ewhAllDone.Wait();

3) DoSomething should look like

{
    try
    {
        // some long executing code
    }
    finally
    {
        // ....
        PostActionCheck();
    }
} 

4) where PostActionCheck is

void PostActionCheck()
{
    if (Interlocked.Decrement(ref ActiveCount) == 0)
        ewhAllDone.Set();
}

Idea

ActiveCount is initialized with 1, and then get incremented n times.

PostActionCheck is called n + 1 times. The last one will trigger the event.

The benefit of this solution is that is uses a single kernel object (which is an event), and 2 * n + 1 calls of lightweight API's. (Can there be less ?)

P.S.

I wrote the code here, I might have misspelled some class names.

3
ответ дан 29 November 2019 в 04:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: