.NET: Как получить данные основного потока сигнала фонового потока?
Какова правильная техника для того, чтобы иметь ThreadA сигнал ThreadB какого-либо события, без блокировки ThreadB, ожидающего события?
У меня есть фоновый поток, который будет заполнять общий список <T> . Я пытаюсь найти способ асинхронно сигнализировать "основной" поток, что есть данные, которые можно получить.
Я рассмотрел возможность установки события с объектом EventWaitHandle, но я не могу использовать мой основной поток в Event.WaitOne().
Я считал, что имеет обратный вызов делегата, но
a) я не хочу, чтобы основной поток выполнял работу в делегате: поток должен вернуться к работе, добавляя больше вещей - я не хочу, чтобы он ждал, пока делегат выполняет, и
b) делегат должен быть подключен к основному потоку, но я не управляю пользовательским интерфейсом, у меня нет элемента управления для. Включение делегата против.
Я рассмотрел запрос обратного вызова делегата, который просто запускает нулевой интервал System.Windows.Forms.Timer(с доступом потока к синхронизированному таймеру). Таким образом, поток нужно только застревать, поскольку он вызывает
Timer.Enabled = true;
но это похоже на взлом.
В прежние дни мой объект создавал бы скрытое окно и имел сообщения с сообщениями о потоке в эти скрытые окна "HWND". Я считал создание скрытого элемента управления, но я понимаю, что вы не можете. Включение элемента управления без создания дескриптора. Кроме того, у меня нет интерфейса: мой объект мог быть создан на веб-сервере, службе или консоли, я не хочу, чтобы появился графический элемент управления, и я не хочу компилировать зависимость от System.Windows. Формы.
Я считал, что мой объект выставляет интерфейс ISynchronizeInvoke, но тогда мне нужно будет реализовать .Invoke() и эту проблему.
Какова надлежащая техника для потока Thread Сигнальная нить B какого-либо события, без блокировки потока B, ожидающего события?
Ответы
Ответ 1
Вот пример кода для класса 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.");
}
Ответ 2
Я собираю несколько ответов здесь.
В идеальной ситуации используется флаговый флажок, например AutoResetEvent
. Вам не нужно блокировать бесконечно, когда вы вызываете WaitOne()
, на самом деле у него есть перегрузка, которая позволяет вам указать тайм-аут. Эта перегрузка возвращает false
, если флаг не был установлен в течение интервала.
A Queue
является более идеальной структурой для отношений между производителем и потребителем, но вы можете имитировать ее, если ваши требования вынуждают вас использовать List
. Основное различие заключается в том, что вам нужно будет обеспечить, чтобы ваш потребитель блокировал доступ к коллекции при ее извлечении; самая безопасная вещь - вероятно использовать метод CopyTo
для копирования всех элементов в массив, а затем отпустите блокировку. Разумеется, убедитесь, что ваш продюсер не будет пытаться обновить List
во время блокировки.
Здесь представлено простое консольное приложение С#, демонстрирующее, как это можно реализовать. Если вы играете с временными интервалами, вы можете вызвать различные вещи; в этой конкретной конфигурации я пытался заставить продюсер создать несколько элементов, прежде чем потребитель проверит элементы.
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
. Производитель будет по умолчанию хранить элементы в приватном буфере, а затем попытаться записать их в общий буфер. Когда потребитель работает с общим буфером, он сбрасывает флаг на объект-производитель. Прежде чем производитель попытается переместить элементы из частного буфера в общий буфер, он проверяет этот флаг и копирует только те экземпляры, когда потребитель не работает над ним.
Ответ 3
Если вы используете фонового рабочего для запуска второго потока и используете событие ProgressChanged, чтобы уведомить другой поток о том, что данные готовы. Другие события также доступны. Эта статья MSDN должна помочь вам начать.
Ответ 4
Есть много способов сделать это, в зависимости от того, что вы хотите сделать. A очередь производителей/потребителей, вероятно, вы хотите. Для отличного углубленного изучения потоков см. Главу Threading (доступна онлайн) из отличной книги С# 3.0 в двух словах.
Ответ 5
Вы можете использовать AutoResetEvent (или ManualResetEvent). Если вы используете AutoResetEvent.WaitOne(0, false), он не будет блокироваться. Например:
AutoResetEvent ev = new AutoResetEvent(false);
...
if(ev.WaitOne(0, false)) {
// event happened
}
else {
// do other stuff
}
Ответ 6
В этом случае отвечает класс 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()
Ответ 7
Если ваш "основной" поток является потоком потока сообщений Windows (GUI), вы можете опросить с помощью формы. Timer - настройте интервал таймера в соответствии с тем, как быстро вам нужно, чтобы поток вашего GUI "уведомлял" данные из рабочий поток.
Не забудьте синхронизировать доступ к общему List<>
, если вы собираетесь использовать foreach
, чтобы избежать исключений CollectionModified
.
Я использую этот метод для всех графических интерфейсов, основанных на рыночных данных, в торговом приложении реального времени, и он работает очень хорошо.