Создайте несколько потоков для работы, затем подождите, пока все не закончится - PullRequest
53 голосов
/ 27 марта 2010

просто нужен совет по "лучшей практике" в отношении многопоточных задач.

Например, у нас есть приложение на C #, которое при запуске считывает данные из различных таблиц типов в нашей базе данных и сохраняет информацию в коллекции, которую мы передаем приложению. это предотвращает попадание в базу данных каждый раз, когда требуется эта информация.

на данный момент приложение читает данные из 10 таблиц синхронно. Мне бы очень хотелось, чтобы приложение считывалось из каждой таблицы в отдельном потоке, работающем параллельно. приложение будет ожидать завершения всех потоков, прежде чем продолжить запуск приложения.

Я заглянул в BackGroundWorker, но просто хочу получить совет по выполнению вышеуказанного.

  1. Звучит ли метод логично, чтобы ускорить время запуска нашего приложения
  2. Как мы можем лучше всего обрабатывать все потоки, учитывая, что работа каждого потока независима друг от друга, нам просто нужно дождаться завершения всех потоков, прежде чем продолжить.

Я с нетерпением жду некоторых ответов

Ответы [ 12 ]

92 голосов
/ 03 апреля 2010

Я предпочитаю обрабатывать это через один WaitHandle и использовать Interlocked, чтобы избежать блокировки на счетчике:

class Program
{
    static void Main(string[] args)
    {
        int numThreads = 10;
        ManualResetEvent resetEvent = new ManualResetEvent(false);
        int toProcess = numThreads;

        // Start workers.
        for (int i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // If we're the last thread, signal
                if (Interlocked.Decrement(ref toProcess) == 0)
                    resetEvent.Set();
            }).Start();
        }

        // Wait for workers.
        resetEvent.WaitOne();
        Console.WriteLine("Finished.");
    }
}

Это хорошо работает и масштабируется до любого количества потоков обработки без введения блокировки.

28 голосов
/ 20 июня 2010

Мне нравится решение @ Рида. Другой способ сделать то же самое в .NET 4.0 - использовать CountdownEvent .

class Program
{
    static void Main(string[] args)
    {
        var numThreads = 10;
        var countdownEvent = new CountdownEvent(numThreads);

        // Start workers.
        for (var i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // Signal the CountdownEvent.
                countdownEvent.Signal();
            }).Start();
        }

        // Wait for workers.
        countdownEvent.Wait();
        Console.WriteLine("Finished.");
    }
}
9 голосов
/ 27 марта 2010

Если у вас более 64 дескрипторов ожидания для потока STA, как говорит Марк. Вы можете создать список с вашими потоками и подождать, пока все завершится во втором цикле.

//check that all threads have completed.
foreach (Thread thread in threadList)
{
     thread.Join();

}  
7 голосов
/ 27 марта 2010

Если вы не используете .NET 4.0, вы можете использовать список <<a href="http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent.aspx" rel="nofollow noreferrer"> ManualResetEvent >, по одному для каждого потока и Подождите , чтобы они стали Установить . Для ожидания в нескольких потоках вы можете использовать WaitAll , но обратите внимание на ограничение в 64 дескриптора ожидания. Если вам нужно больше, чем это, вы можете просто зациклить их и ждать каждого из них в отдельности.

Если вы хотите более быстрый опыт запуска, вам, вероятно, не нужно ждать, пока все данные будут прочитаны во время запуска. Просто отобразите графический интерфейс, и любая информация, которая отсутствует, может быть показана серым цветом с каким-то значком «Обновление ...» или подобным. Когда информация поступает, просто запустите событие, чтобы обновить графический интерфейс. Пользователь может начать выполнять множество операций даже до того, как будут прочитаны все данные из всех таблиц.

6 голосов
/ 27 марта 2010

Если вы любите приключения, вы можете использовать C # 4.0 и параллельную библиотеку задач:

Parallel.ForEach(jobList, curJob => {
  curJob.Process()
});
4 голосов
/ 15 ноября 2010

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

int threadCount = 1;
ManualResetEvent finished = new ManualResetEvent(false);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
  Interlocked.Increment(ref threadCount); 
  ThreadPool.QueueUserWorkItem(delegate 
  { 
      try 
      { 
           // do work 
      } 
      finally 
      { 
          if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
      } 
  }); 
}
if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
finished.WaitOne(); 

В качестве личного предпочтения мне нравится использовать класс CountdownEvent для подсчета, который доступен в .NET 4.0.

var finished = new CountdownEvent(1);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
  finished.AddCount();
  ThreadPool.QueueUserWorkItem(delegate 
  { 
      try 
      { 
           // do work 
      } 
      finally 
      { 
        finished.Signal();
      } 
  }); 
}
finished.Signal();
finished.Wait(); 

В приведенных выше примерах используется ThreadPool, но вы можете поменять его на любой механизм потоков, который вы предпочитаете.

2 голосов
/ 09 июня 2014

Другая возможность с TPL , при условии, что jobs - это наборы элементов для обработки или подпотоки для запуска:

Task.WaitAll(jobs
    .Select(job => TaskFactory.StartNew(() => /*run job*/))
    .ToArray());
2 голосов
/ 01 апреля 2010

Просто для удовольствия, что сделал @Reed с Monitor. : P

class Program
{
    static void Main(string[] args)
    {
        int numThreads = 10;
        int toProcess = numThreads;
        object syncRoot = new object();

        // Start workers.
        for (int i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // If we're the last thread, signal
                if (Interlocked.Decrement(ref toProcess) == 0)
                {
                    lock (syncRoot)
                    {
                        Monitor.Pulse(syncRoot);
                    }
                }
            }).Start();
        }

        // Wait for workers.
        lock (syncRoot)
        {
            if (toProcess > 0)
            {
                Monitor.Wait(syncRoot);
            }
        }

        Console.WriteLine("Finished.");
    }
}
1 голос
/ 27 марта 2010

Предполагая, что потоки считывателя базы данных возвращаются, как только они завершатся, вы можете просто вызвать Thread.Join для всех десяти потоков по очереди из исходного потока.

0 голосов
/ 11 июля 2012

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

while (threadCounter > 0)
{
    Thread.Sleep(500); //Make it pause for half second so that we don’t spin the cpu out of control.
}

Задокументировано в моем блоге. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

...