.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 должна помочь вам начать.

Ответ 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.

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