Какой полезный шаблон для ожидания завершения всех потоков? - PullRequest
2 голосов
/ 18 февраля 2010

У меня есть сценарий, в котором мне придется запустить тонну потоков (возможно, до 100), затем дождаться их завершения, а затем выполнить задачу (в еще одном потоке).

Каков принятый образец для выполнения этого типа работы? Это просто. Присоединяйся? Или сегодня существует более высокий уровень абстракции?

Использование .NET 2.0 с VS2008.

Ответы [ 4 ]

4 голосов
/ 18 февраля 2010

В .NET 3.5sp1 или .NET 4, TPL сделает это намного проще.Однако я приспособлю это только к возможностям .NET 2.

Есть несколько вариантов.Использование Thread.Join вполне приемлемо, особенно если все потоки создаются вами вручную.Это очень просто, надежно и просто для реализации.Вероятно, это был бы мой выбор.

Однако другой вариант - создать счетчик для общего объема работы и использовать событие сброса, когда счетчик достигает нуля.Например:

class MyClass {
    int workToComplete; // Total number of elements
    ManualResetEvent mre; // For waiting

    void StartThreads()
    {
        this.workToComplete = 100;
        mre = new ManualResetEvent(false);

        int total = workToComplete;
        for(int i=0;i<total;++i)
        {
             Thread thread = new Thread( new ThreadStart(this.ThreadFunction) );
             thread.Start(); // Kick off the thread
        }

        mre.WaitOne(); // Will block until all work is done
    }

    void ThreadFunction()
    {
        // Do your work

        if (Interlocked.Decrement(ref this.workToComplete) == 0)
            this.mre.Set(); // Allow the main thread to continue here...
    }
}
1 голос
/ 18 февраля 2010

Я не уверен, что какие-либо стандартные механизмы блокировки или синхронизации потоков действительно будут работать с таким количеством потоков. Однако это может быть сценарий, в котором некоторые базовые сообщения могут быть идеальным решением проблемы.

Вместо использования Thread.Join, который блокирует (и может быть очень трудно управлять с таким количеством потоков), вы можете попробовать настроить еще один поток, который объединяет сообщения о завершении из ваших рабочих потоков. Когда агрегатор получил все ожидаемые сообщения, он завершается. Затем вы можете использовать один WaitHandle между агрегатором и основным потоком приложения, чтобы сигнализировать, что все ваши рабочие потоки выполнены.

public class WorkerAggregator
{
    public WorkerAggregator(WaitHandle completionEvent)
    {
        m_completionEvent = completionEvent;
        m_workers = new Dictionary<int, Thread>();
    }

    private readonly WaitHandle m_completionEvent;
    private readonly Dictionary<int, Thread> m_workers;

    public void StartWorker(Action worker)
    {
        var thread = new Thread(d =>
            {
                worker();
                notifyComplete(thread.ManagedThreadID);
            }
        );

        lock (m_workers)
        {
            m_workers.Add(thread.ManagedThreadID, thread);
        }

        thread.Start();
    }

    private void notifyComplete(int threadID)
    {
        bool done = false;
        lock (m_workers)
        {
            m_workers.Remove(threadID);
            done = m_workers.Count == 0;
        }

        if (done) m_completionEvent.Set();
    }
}

Обратите внимание, я не проверял приведенный выше код, поэтому он может быть неверен на 100%. Тем не менее, я надеюсь, что это иллюстрирует концепцию достаточно, чтобы быть полезным.

1 голос
/ 18 февраля 2010

Что для меня хорошо, так это сохранять ManagedThreadId каждого потока в словаре, когда я его запускаю, и затем каждый поток передает свой идентификатор обратно через метод обратного вызова, когда он завершает работу. Метод обратного вызова удаляет идентификатор из словаря и проверяет свойство Count словаря; когда это ноль, вы сделали. Обязательно зафиксируйте словарь как для добавления, так и удаления из него.

1 голос
/ 18 февраля 2010

Вы смотрели на ThreadPool?Выглядит как здесь - Руководство по ThreadPool , avtor решает ту же задачу, что и вы.

...