2010-01-08 4 views
1

Я хочу написать свое первое настоящее приложение MultiThreaded C#. Хотя раньше я использовал BackgroundWorker и знал кое-что о блокировке (объекте), я никогда не использовал объект Thread, Monitor.Enter и т. Д., И я полностью потерял место, где приступить к проектированию архитектуры.Базовая архитектура и жизненный цикл потоков в C# /. Net 3.5SP1

По существу моя программа работает в фоновом режиме. Каждые 5 минут он проверяет веб-службу. Если веб-служба возвращает данные, она создает задания из этих данных и передает их в JobQueue. Затем JobQueue последовательно работает над этими заданиями - если новое задание добавляется, пока оно все еще работает над ним, оно будет стоять в очереди на задание. Кроме того, есть веб-сервер, позволяющий удаленный доступ к программе.

Как я понимаю, мне нужно 4 Темы:

  1. Главный поток
  2. "5-минутный Таймер" и WebService Thread
  3. JobQueue
  4. Веб-сервер

Резьба 2-4 должна быть создана, когда программа запускается и заканчивается, когда программа заканчивается, поэтому они запускаются только один раз.

Как уже говорилось, я действительно не знаю, как будет работать архитектура на этом. Что бы тему 1 сделала? Когда экземпляр класса MyProgram создается, должен ли он иметь Queue<Job> как свойство? Как мне начать свою тему? Насколько я вижу, мне нужно передать функцию в поток - где должна сидеть эта функция? Если у меня есть класс «MyJobQueueThreadClass», который имеет все функции для Thread 3, как бы получить доступ к объекту в классе MyProgram? И если Thread - это просто функция, как я могу предотвратить ее досрочное завершение? Как сказано, Thread 2 ждет 5 минут, затем выполняет ряд функций и перезапускает 5-минутный таймер (Thread.Sleep (300)?) Снова и снова, пока моя программа не закончится (вызовите Thread.Abort (Thread2) в Close/Exit/Destructor MyProgram?)

+0

Извините, что есть несколько вопросов, но, как сказал, что я своего рода потерял. Я попытался немного поработать, но это всегда немного сложно, так как я не могу сказать, относится ли статья к 3.5 SP1 или написана для 1.1 (я знаю, что были изменения, т.е. ReaderWriterLockSlim, но я не могу их помещать в перспективы). Кроме того, без какого-либо способа обнаружить неправильную информацию (которой много), Google Googling всегда похож на бафф-блейзер в минном поле ... –

ответ

16

Давайте пройти через это, шаг за шагом:

1.

class Program { 

Очередь задания является структурой данных:

private static Queue<Job> jobQueue; 

Если структура данных доступны несколько потоков , вам необходимо установить блокировка it:

private static void EnqueueJob(Job job) { 
     lock (jobQueue) { 
      jobQueue.Enqueue(job); 
     } 
    } 

    private static Job DequeueJob() { 
     lock (jobQueue) { 
      return jobQueue.Dequeue(); 
     } 
    } 

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

private static void RetrieveJob(object unused) { 
     Job job = ... // retrieve job from webservice 
     EnqueueJob(job); 
    } 

И метод, который обрабатывает задания в очереди в цикле:

private static void ProcessJobs() { 
     while (true) { 
      Job job = DequeueJob(); 
      // process job 
     } 
    } 

Давайте рассмотрим эту программу:

private static void Main() { 
     // run RetrieveJob every 5 minutes using a timer 
     Timer timer = new Timer(RetrieveJob); 
     timer.Change(TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(5)); 

     // run ProcessJobs in thread 
     Thread thread = new Thread(ProcessJobs); 
     thread.Start(); 

     // block main thread 
     Console.ReadLine(); 
    } 
} 

2.

Если вы запустите программу, вы заметите, что задание добавляется каждые 5 минут. Но jobQueue.Dequeue() будет вызывать InvalidOperationException, потому что очередь заданий пуста, пока не будет получено задание.

Чтобы исправить это, мы превращаем очереди заданий в очереди блокировки с помощью Семафор:

private static Semaphore semaphore = new Semaphore(0, int.MaxValue); 

    private static void EnqueueJob(Job job) { 
     lock (jobQueue) { 
      jobQueue.Enqueue(job); 
     } 
     // signal availability of job 
     semaphore.Release(1); 
    } 

    private static Job DequeueJob() { 
     // wait until job is available 
     semaphore.WaitOne(); 
     lock (jobQueue) { 
      return jobQueue.Dequeue(); 
     } 
    } 

3.

Если запустить программу еще раз, он не будет бросать исключение, и все должно работать нормально. Но вы заметите, что вам нужно убить процесс, потому что поток ProcessJobs никогда не заканчивается. Итак, как вам закончить свою программу?

Я рекомендую вам определить специальную работу, которая указывает на окончание обработки задания:

private static void ProcessJobs() { 
     while (true) { 
      Job job = DequeueJob(); 
      if (job == null) { 
       break; 
      } 
      // process job 
     } 
     // when ProcessJobs returns, the thread ends 
    } 

Затем остановить таймер и добавить специальную работу в очередь заданий:

private static void Main() { 
     // run RetrieveJob every 5 minutes using a timer 
     Timer timer = new Timer(RetrieveJob); 
     timer.Change(TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(5)); 

     // run ProcessJobs in thread 
     Thread thread = new Thread(ProcessJobs); 
     thread.Start(); 

     // block main thread 
     Console.ReadLine(); 

     // stop the timer 
     timer.Change(Timeout.Infinite, Timeout.Infinite); 

     // add 'null' job and wait until ProcessJobs has finished 
     EnqueueJob(null); 
     thread.Join(); 
    } 

Я надеюсь, что это косвенно отвечает на все ваши вопросы :-)

правил эмпирического

  • Начало нить, указав метод, который имеет доступ ко всем необходимым структурам данных

    • Использование ThreadPool.QueueUserWorkItem для небольших задач
    • Используйте Timer для небольших периодических задач
    • Используйте Thread для долго- выполняемые задачи
  • При доступе к структурам данных из нескольких потоков вам необходимо указать loc K структуры данных

    • В большинстве случаев lock заявления будет делать
    • Используйте ReaderWriterLockSlim, если есть много потоков чтения из структуры данных, которая редко изменяются.
    • Вам не нужна блокировка, если структура данных является неизменной.
  • Когда несколько потоков зависят друг от друга (например, поток ждет другого потока, чтобы завершить задачу) использовать сигналы

  • Do не использовать Thread.Abort, Thread.interrupt, Thread.Resume, Thread.Sleep, Thread.Suspend, Monitor.Pulse, Monitor.Wait

+2

+1 для темы ** thread.Join() ** Я видел много людей забыв об этом. Хорошо объяснил. –

+1

Я бы сделал старт и остановил явные методы вместо использования нулевого сигнала. в противном случае, очень хороший ответ – 2010-01-08 06:49:48

+0

Wow, many Thanks! Прочитав это сейчас, но это очень полезно! –

2

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

Вы можете настроить объект таймера (System.Threading.Timer) на основной поток, который проходит каждые 5 минут - поток из пула потоков .NET будет использоваться для возврата обратно в обработчик прошедшего события.

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

Затем вы хотите создать поток, который выдает задания из очереди и обрабатывает их - вы можете реализовать это как класс, который инкапсулирует экземпляр Thread, используя шаблон рабочего потока. Его функция заключается в том, чтобы поп-задания покидать очередь заданий и обрабатывать их, а также переходить в спящий режим на время, когда работа выполнена (очередь пуста). Когда поток просыпается, он возобновит цикл, в котором он остановился, или пока он не будет сигнализирован о остановке основного потока.

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

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

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