.СЕТЬ:Как обеспечить доступность данных основного потока фонового сигнала потока?

StackOverflow https://stackoverflow.com/questions/122882

Вопрос

Какова правильная техника ТемаА сигнал Поток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 исключения.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top