2010-02-03 4 views
12

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

В моем случае, в основном, у меня есть класс контроллера, в котором я хочу развернуть несколько рабочих потоков, чтобы выполнить некоторую обработку (ограничение максимального числа рабочих потоков, порожденных с помощью семафора). Затем я хочу, чтобы мой класс контроллера блокировался, пока все потоки не завершили обработку. Поэтому после того, как я начинаю рабочий поток, чтобы выполнить некоторую работу, я хочу, чтобы поток мог уведомлять поток контроллера, когда обработка завершена. Я вижу, что я могу использовать класс рабочего класса, и обрабатывать события DoWork и RunWorkerCompleted для выполнения этого, однако интересно было ли это хорошая идея? Есть ли лучшие способы достичь этого?

+2

Возможно, это может не помочь вам решить эту проблему, но вам могут заинтересовать новые функции параллельного программирования .NET Framework 4. Что-то, на что нужно следить - скоро выйдет. http://msdn.microsoft.com/en-us/concurrency/default.aspx –

ответ

8

Это не будет работать в консольном приложении, так как SynchronizationContext.Current никогда не будет инициализирован. Это инициализируется Windows Forms или WPF для вас, когда вы используете графическое приложение.

Это, как говорится, нет причин для этого. Просто используйте ThreadPool.QueueUserWorkItem и событие сброса (ManualResetEvent или AutoResetEvent), чтобы уловить состояние завершения и заблокировать основной поток.


Edit:

После просмотра некоторых из комментариев О.П., я думал, что я хотел бы добавить это.

«Лучшая» альтернатива, на мой взгляд, заключалась бы в том, чтобы получить копию Rx Framework, поскольку она включает в себя backport TPL в .NET 4. Это позволило бы использовать перегрузку Parallel.ForEach, которая обеспечивает вариант поставки экземпляра ParallelOptions. Это позволит ограничить общее количество одновременных операций, и обрабатывать все работы для вас:

// using collection of work items, such as: List<Action<object>> workItems; 
var options = new ParallelOptions(); 
options.MaxDegreeOfParallelism = 10; // Restrict to 10 threads, not recommended! 

// Perform all actions in the list, in parallel 
Parallel.ForEach(workItems, options, item => { item(null); }); 

Однако, используя Parallel.ForEach, я бы лично позволить системе управлять степенью параллелизма. Он автоматически назначит соответствующее количество потоков (особенно когда/если это переместится на .NET 4).

9

Если ваше требование просто блокировать, пока все нити не закончили, что это очень легко - просто начать новые темы, а затем вызвать Thread.Join по каждому из них:

using System; 
using System.Collections.Generic; 
using System.Threading; 

public class Test 
{ 
    static void Main() 
    { 
     var threads = new List<Thread>(); 
     for (int i = 0; i < 10; i++) 
     { 
      int copy = i; 
      Thread thread = new Thread(() => DoWork(copy)); 
      thread.Start(); 
      threads.Add(thread); 
     } 

     Console.WriteLine("Main thread blocking"); 
     foreach (Thread thread in threads) 
     { 
      thread.Join(); 
     } 
     Console.WriteLine("Main thread finished"); 
    } 

    static void DoWork(int thread) 
    { 
     Console.WriteLine("Thread {0} doing work", thread); 
     Random rng = new Random(thread); // Seed with unique numbers 
     Thread.Sleep(rng.Next(2000)); 
     Console.WriteLine("Thread {0} done", thread); 
    } 
} 

EDIT: Если у вас есть доступ. NET 4.0, то TPL, безусловно, правильный путь. В противном случае я бы предложил использовать очередь производителей/потребителей (есть много примеров кода). В основном у вас есть очередь рабочих элементов и столько потребительских потоков, сколько у вас есть ядра (при условии, что они связаны с процессором, вы хотите настроить его на свою рабочую нагрузку). Каждый потребительский поток будет брать элементы из очереди и обрабатывать их по одному за раз. Точно как вы справляетесь с этим, это будет зависеть от вашей ситуации, но это не очень сложно. Это еще проще, если вы можете придумать всю работу, которую вам нужно сделать, чтобы начать, чтобы потоки могли просто выйти, когда они обнаружили, что очередь пуста.

+0

Это добавляет довольно много времени запуска с использованием потоков ThreadPool. Начать собственный поток не быстро и, вероятно, не нужно. –

+1

@Reed: если пул потоков уже не разогрет, эта стоимость будет там в любом случае ... и это действительно не *, что очень дорого запускать потоки. На моем ** нетбуке ** требуется менее 1 секунды для запуска и объединения 1000 потоков. Я не считаю это «совсем немного времени запуска» - и маловероятно, что OP действительно понадобится 1000 потоков в любом случае. Я рассматриваю это как более простое решение, чем использование пула потоков. –

+0

Jon, но с thread.join, я просто блокирую основную нить, пока она не закончится. Однако что, если я хочу, чтобы меня уведомляли, когда конкретный поток завершается, поэтому я могу сделать еще одну операцию в основном потоке? – jvtech

1

Вы правы, что BackgroundWorker здесь не работает. Это действительно предназначено для работы с средами графического интерфейса (WinForms и WPF), где основной поток постоянно занят проверкой/выполнением Message Pump (который обрабатывает все события Windows, такие как Click и Resize, а также те, которые отправляются из backgroundWorker). Без очереди на событие Mainthread закончится. И без очереди событий BackgroundWorker также не может вызывать обратные вызовы в главной теме.

Действительно, многопоточность на консоли в десятки раз сложнее, потому что у вас нет Message Pump для решения двух больших проблем: как сохранить главный поток и как сообщить основной поток изменений, необходимых для пользовательского интерфейса , Программирование, основанное на событиях, было идеальным местом для многопоточности.

Есть несколько решений:

Расширение вашего приложения консоли с Message Pump. Поскольку это немного больше, чем в Thread общей коллекции делегатов (где другие темы только добавить материал) и время цикла это удивительно просто: C# Console App + Event Handling

Классы Threadpool и темы. Они были до тех пор, как BackgroundWorker (начиная с .NET 2.0) и, похоже, предназначены для решения проблемы для консоли. Вам все равно нужно заблокировать основной поток контуром. ThreadPool также ограничивает количество потоков для чего-то, что можно использовать на реальной машине. Вам следует избегать фиксированных ограничений по потоку и вместо этого полагаться на функции ThreadPooling для ОС, чтобы решить вопрос «сколько потоков должно выполняться сразу?». Например, фиксированное число из 5 нитей может быть идеальным для вашей 6-й базовой машины разработки. Но для одного основного настольного компьютера это будет слишком много. И было бы бессмысленным «Узким местом» на сервере, который легко мог бы переместиться между 16 и 64 ядрами с ОЗУ RAM.

Если у вас есть .NET 4.0 или более поздняя версия, новый параллелизм задач может оказаться полезным. http://msdn.microsoft.com/en-us/library/dd537609.aspx Он построил в ThreadPooling, ограниченные возможности для определения приоритетов и может легко и быстро присоединиться к нескольким задачам (все, что может сделать Thread, Task может сделать). Использование задачи также позволяет использовать async и ждать Ключевые слова: http://msdn.microsoft.com/en-us/library/hh191443.aspx Опять же, вам не обойти блокировку главной нити в консольном приложении.

+0

На самом деле событие очень прост в использовании и очень эффективный способ выполнения консольной программы, пока не произойдет что-то конкретное. – user34660

Смежные вопросы