.СЕТЬ:Как обеспечить доступность данных основного потока фонового сигнала потока?
-
02-07-2019 - |
Вопрос
Какова правильная техника ТемаА сигнал ПотокB какого-то события, не имея ПотокB сидеть сложа руки в ожидании какого-то события?
у меня есть фоновый поток, который будет заполнять общий список List<T>.я пытаюсь найти способ асинхронно сигнализировать «основному» потоку о наличии данных, которые можно получить.
Я рассматривал возможность установки события с помощью объекта EventWaitHandle, но не могу, чтобы мой основной поток находился в Event.WaitOne().
Я подумал о том, чтобы иметь обратный вызов делегата, но а) Я не хочу, чтобы основная тема работала в делегате:Поток должен вернуться к работе, добавляя больше вещей - я не хочу, чтобы он ждал, пока делегат выполняется, и б) делегат должен быть маршал на главную ветку, но я не запускаю пользовательский интерфейс, у меня нет Контроль, чтобы. INVOKE делегата против.
Я рассматривал возможность обратного вызова делегата, который просто запускает нулевой интервал System.Windows.Forms.Timer (с синхронизированным доступом потока к таймеру).Таким образом, поток должен только застревать во время вызова
Timer.Enabled = true;
но это похоже на хак.
Раньше мой объект создавал бы скрытое окно, и поток отправлял бы сообщения в HWND этого скрытого окна.Я подумал о создании скрытого элемента управления, но понял, что вы не можете .Вызвать элемент управления без созданного дескриптора.Плюс у меня нет пользовательского интерфейса:мой объект мог быть создан на веб-сервере, в службе или консоли, я не хочу, чтобы появлялся графический элемент управления, и я не хочу компилировать зависимость от System.Windows.Forms.
я рассматривал возможность предоставления моему объекту интерфейса ISynchronizeInvoke, но тогда мне нужно было бы реализовать .Invoke(), и это моя проблема.
Каков правильный метод, чтобы поток A сигнализировал потоку B о каком-то событии, не блокируя поток B в ожидании события?
Решение
Ниже приведен пример кода для класса 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.");
}
Другие советы
Я объединяю здесь несколько ответов.
В идеальной ситуации используется флаг потокобезопасности, такой как AutoResetEvent
.Вам не нужно блокировать на неопределенный срок, когда вы звоните WaitOne()
, на самом деле у него есть перегрузка, позволяющая указать таймаут.Эта перегрузка возвращает false
если флаг не был установлен в течение интервала.
А 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
.Производитель по умолчанию сохраняет элементы в частном буфере, а затем пытается записать их в общедоступный буфер.Когда потребитель работает с общедоступным буфером, он сбрасывает флаг объекта-производителя.Прежде чем производитель попытается переместить элементы из частного буфера в общедоступный буфер, он проверяет этот флаг и копирует элементы только тогда, когда потребитель над ними не работает.
Если вы используете фоновый рабочий процесс для запуска второго потока и используете событие ProgressChanged, чтобы уведомить другой поток о готовности данных.Доступны и другие мероприятия. ЭТА статья MSDN должна помочь вам начать работу.
Есть много способов сделать это, в зависимости от того, что именно вы хотите сделать.А очередь производителя/потребителя вероятно, это то, что вы хотите.Для получения более подробной информации о темах см. главу, посвященную Резьба (доступно онлайн) из превосходной книги C# 3.0 в двух словах.
Вы можете использовать AutoResetEvent (или ManualResetEvent).Если вы используете AutoResetEvent.WaitOne(0, false), он не будет блокироваться.Например:
AutoResetEvent ev = new AutoResetEvent(false);
...
if(ev.WaitOne(0, false)) {
// event happened
}
else {
// do other stuff
}
В этом случае ответом является класс BackgroundWorker.Это единственная потоковая конструкция, которая может асинхронно отправлять сообщения в нить который создал объект BackgroundWorker.Внутренне BackgroundWorker
использует AsyncOperation
класс, позвонив asyncOperation.Post()
метод.
this.asyncOperation = AsyncOperationManager.CreateOperation(null);
this.asyncOperation.Post(delegateMethod, arg);
Несколько других классов в .NET Framework также используют AsyncOperation:
- ФонРабочий
- СаундПлеер.LoadAsync()
- СмтпКлиент.SendAsync()
- Пинг.SendAsync()
- ВебКлиент.ЗагрузитьДанныеАсинк()
- ВебКлиент.ЗагрузитьФайл()
- ВебКлиент.DownloadFileAsync()
- Веб-клиент...
- PictureBox.LoadAsync()
Если ваш «основной» поток является потоком насоса сообщений Windows (GUI), то вы можете опросить его с помощью Forms.Timer — настроить интервал таймера в соответствии с тем, как быстро вам нужно, чтобы поток графического интерфейса «замечал» данные из рабочего потока. .
Не забудьте синхронизировать доступ к общим List<>
если вы собираетесь использовать foreach
, избегать CollectionModified
исключения.
Я использую этот метод для всех обновлений графического интерфейса на основе рыночных данных в торговом приложении в реальном времени, и он работает очень хорошо.