.NET: Как сделать так, чтобы сигнал основного потока имел доступ к данным основного потока? - PullRequest
11 голосов
/ 23 сентября 2008

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

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


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


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


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

Timer.Enabled = true;

но это похоже на взлом.

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


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


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

Ответы [ 7 ]

11 голосов
/ 24 сентября 2008

Вот пример кода для класса 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.");
    }
3 голосов
/ 24 сентября 2008

Я объединяю несколько ответов здесь.

В идеальной ситуации используется потокобезопасный флаг, такой как 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. По умолчанию производитель сохранит элементы в личном буфере, а затем попытается записать их в общий буфер. Когда потребитель работает с публичным буфером, он сбрасывает флаг объекта-производителя. Прежде чем производитель пытается переместить элементы из частного буфера в открытый, он проверяет этот флаг и копирует элементы только тогда, когда потребитель не работает с ним.

1 голос
/ 24 сентября 2008

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

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

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

  • BackgroundWorker
  • SoundPlayer.LoadAsync ()
  • SmtpClient.SendAsync ()
  • Ping.SendAsync ()
  • WebClient.DownloadDataAsync ()
  • WebClient.DownloadFileAsync ()
  • WebClient ...
  • PictureBox.LoadAsync ()
1 голос
/ 23 сентября 2008

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

AutoResetEvent ev = new AutoResetEvent(false);
...
if(ev.WaitOne(0, false)) {
  // event happened
}
else {
 // do other stuff
}
1 голос
/ 23 сентября 2008

Есть много способов сделать это, в зависимости от того, что именно вы хотите сделать. очередь производителя / потребителя , вероятно, то, что вы хотите. Для превосходного углубленного изучения потоков см. Главу Threading (доступна онлайн) из превосходной книги C # 3.0 в двух словах .

1 голос
/ 23 сентября 2008

Если вы используете backgroundworker для запуска второго потока и используете событие ProgressChanged, чтобы уведомить другой поток о готовности данных. Другие события также доступны. Эта статья MSDN должна помочь вам начать .

0 голосов
/ 24 сентября 2008

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

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

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

...