.NET: Как получить данные основного потока сигнала потока фона?


10

Какова правильная техника иметь ThreadA сигнала ThreadB некоторого события, не имея ThreadB сидеть заблокировали ждет событие произойдет?

У меня есть фоновый поток, который будет заполнять общий список <T>. Я пытаюсь найти способ асинхронно сигнализировать «основной» поток, что есть данные, которые можно получить.


я рассмотрел установки события с объектом EventWaitHandle, но я не могу иметь мой основной поток сидит на Event.WaitOne().


я рассмотрел имеющие делегат обратного вызова, но а) я не хочу, чтобы основной поток выполняет работу в делегатом: поток должен вернуться к работе, добавив больше вещей - я не хочу ожидая, пока делегат выполнит, и b) делегат должен быть подключен к основному потоку, но у меня нет пользовательского интерфейса, у меня нет элемента управления для .Invoke делегировать.


я рассмотрел есть делегат обратного вызова, который просто запускает интервал System.Windows.Forms.Timer ноль (с доступом потоков синхронизированного таймера). Таким образом, поток должен только быть застрял, как он называет

Timer.Enabled = true;

, но это, кажется, как взломать.

В прежние дни мой объект создал бы скрытое окно и имел сообщения с сообщением о потоке в эти скрытые окна «HWND». Я считал создание скрытого элемента управления, но я понимаю, что вы не можете. Включение элемента управления без создания дескриптора. Кроме того, у меня нет интерфейса: мой объект мог быть создан на веб-сервере, службе или консоли, я не хочу, чтобы появился графический элемент управления, и я не хочу компилировать зависимость от System.Windows. Формы.


я рассмотрел, чтобы мой объект разоблачить интерфейс ISynchronizeInvoke, но тогда я должен был бы реализовать .Invoke(), и это моя проблема.


Какова правильная техника, чтобы иметь нить волоске сигнал B какого-либо события, без резьбы B сидеть заблокировали ждет событие произойдет?

10

Вот пример кода для класса System.ComponentModel.BackgroundWorker.

private static BackgroundWorker worker = new BackgroundWorker(); 
    static void Main(string[] args) 
    { 
     worker.DoWork += worker_DoWork; 
     worker.RunWorkerCompleted += worker_RunWorkerCompleted; 
     worker.ProgressChanged += worker_ProgressChanged; 
     worker.WorkerReportsProgress = true; 

     Console.WriteLine("Starting application."); 
     worker.RunWorkerAsync(); 

     Console.ReadKey(); 
    } 

    static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     Console.WriteLine("Progress."); 
    } 

    static void worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     Console.WriteLine("Starting doing some work now."); 

     for (int i = 0; i < 5; i++) 
     { 
      Thread.Sleep(1000); 
      worker.ReportProgress(i); 
     } 
    } 

    static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     Console.WriteLine("Done now."); 
    } 

1

Если вы используете фонарика для запуска второго потока и используете событие ProgressChanged, чтобы уведомить другой поток о том, что данные готовы. Другие события также доступны. THis MSDN article should get you started.

  0

класс BackgroundWorker, кажется, единственное, что может асинхронно отправил УВЕДОМЛЕНИЯ на * поток *, который создал объект. Внутри он использует объект AsyncOperation, вызывая asyncOperation.Post(). 23 сен. 082008-09-23 21:28:55


1

Существует много способов сделать это, в зависимости от того, что вы хотите сделать. A producer/consumer queue, вероятно, то, что вы хотите. Для отличного углубленного изучения резьбы см. Главу Threading (доступна в Интернете) из отличной книги C# 3.0 in a Nutshell.

  0

В модели производителя/потребителя основной поток создает единицы работы, которые необходимо выполнить. Фоновый поток сидит в ожидании работы в очереди. Когда создается единица работы и устанавливается событие. Это не работает здесь, поскольку основной поток является «потребителем», и он не может сидеть в ожидании работы. 23 сен. 082008-09-23 19:18:57


1

Вы можете использовать AutoResetEvent (или ManualResetEvent). Если вы используете AutoResetEvent.WaitOne (0, false), он не будет блокироваться.Например:

AutoResetEvent ev = new AutoResetEvent(false); 
... 
if(ev.WaitOne(0, false)) { 
    // event happened 
} 
else { 
// do other stuff 
} 
  0

Как вы можете проверить, указано ли событие? т.е. когда? 21 ноя. 082008-11-21 18:22:21


0

Если ваш «основной» поток сообщение насос для Windows (GUI) поток, то вы можете опрашивать с помощью Forms.Timer - настроить интервал таймера в соответствии с тем, как быстро вы должны иметь свой графический интерфейс поток «уведомляет» данные из рабочего потока.

Не забудьте синхронизировать доступ к общему List<>, если вы собираетесь использовать foreach, чтобы избежать CollectionModified исключений.

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


3

Здесь я собрал несколько ответов.

Идеальная ситуация использует флаговый флаг, такой как AutoResetEvent. Вам не нужно блокировать бесконечно, когда вы вызываете WaitOne(), на самом деле у него есть перегрузка, которая позволяет указать тайм-аут. Эта перегрузка возвращает false, если флаг не был установлен в течение интервала.

A Queue является более идеальной структурой для отношений между производителем и потребителем, но вы можете имитировать его, если ваши требования заставляют вас использовать List. Основное различие заключается в том, что вам нужно будет обеспечить, чтобы ваш потребитель блокировал доступ к коллекции, пока он извлекает элементы; самое безопасное - вероятно, использовать метод CopyTo для копирования всех элементов в массив, а затем отпустите блокировку. Разумеется, убедитесь, что ваш продюсер не будет пытаться обновить List во время блокировки.

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

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

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     private static object LockObject = new Object(); 

     private static AutoResetEvent _flag; 
     private static Queue<int> _list; 

     static void Main(string[] args) 
     { 
      _list = new Queue<int>(); 
      _flag = new AutoResetEvent(false); 

      ThreadPool.QueueUserWorkItem(ProducerThread); 

      int itemCount = 0; 

      while (itemCount < 10) 
      { 
       if (_flag.WaitOne(0)) 
       { 
        // there was an item 
        lock (LockObject) 
        { 
         Console.WriteLine("Items in queue:"); 
         while (_list.Count > 0) 
         { 
          Console.WriteLine("Found item {0}.", _list.Dequeue()); 
          itemCount++; 
         } 
        } 
       } 
       else 
       { 
        Console.WriteLine("No items in queue."); 
        Thread.Sleep(125); 
       } 
      } 
     } 

     private static void ProducerThread(object state) 
     { 
      Random rng = new Random(); 

      Thread.Sleep(250); 

      for (int i = 0; i < 10; i++) 
      { 
       lock (LockObject) 
       { 
        _list.Enqueue(rng.Next(0, 100)); 
        _flag.Set(); 
        Thread.Sleep(rng.Next(0, 250)); 
       } 
      } 
     } 
    } 
} 

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

  0

Очередь кажется интересным классом. Хотя я бы не хотел этого в моей ситуации, так как когда-то пользователь может обрабатывать весь список одновременно, вместо того, чтобы деактивировать отдельные элементы. 23 сен. 082008-09-23 21:31:23


1

В данном случае класс BackgroundWorker является ответом. Это единственная потоковая конструкция, которая может асинхронно отправлять сообщения в поток , который создал объект BackgroundWorker. Внутренне BackgroundWorker использует класс AsyncOperation, вызывая метод asyncOperation.Post().

this.asyncOperation = AsyncOperationManager.CreateOperation(null); 
this.asyncOperation.Post(delegateMethod, arg); 

Несколько других классов в рамках .NET также использовать AsyncOperation:

  • BackgroundWorker
  • SoundPlayer.LoadAsync()
  • SmtpClient.SendAsync()
  • Ping.SendAsync()
  • WebClient.DownloadDataAsync()
  • WebClient.DownloadFile()
  • WebClient.DownloadFileAsync()
  • WebClient ...
  • PictureBox.LoadAsync()