Arhn - архитектура программирования

.NET: как получить доступ к данным основного потока сигнала фонового потока?

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

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


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


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


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

Timer.Enabled = true;

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

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


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


Каков правильный метод, чтобы поток A сигнализировал потоку 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.");
    }
23.09.2008

2

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

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

23.09.2008
  • Очередь кажется интересным классом для использования. Хотя я бы не хотел этого в моей ситуации, поскольку иногда пользователь может обрабатывать весь список сразу, вместо того, чтобы удалять отдельные элементы из очереди. 24.09.2008

  • 3

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

    23.09.2008
  • Кажется, что класс BackgroundWorker - единственное, что может асинхронно отправлять уведомление в поток, создавший объект. Внутри он использует объект AsyncOperation, вызывая asyncOperation.Post (). 24.09.2008

  • 4

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

    23.09.2008
  • В модели производитель / потребитель основной поток создает единицы работы, которую необходимо выполнить. Фоновый поток ожидает постановки работы в очередь. Когда единица работы создана и событие установлено. Здесь это не работает, поскольку основной поток - это потребитель, и он не может сидеть в ожидании работы. 23.09.2008

  • 5

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

    AutoResetEvent ev = new AutoResetEvent(false);
    ...
    if(ev.WaitOne(0, false)) {
      // event happened
    }
    else {
     // do other stuff
    }
    
    23.09.2008
  • Как вы проверяете наличие сигнала о событии? т.е. когда? 21.11.2008

  • 6

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

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

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

    • Справочная информация
    • SoundPlayer.LoadAsync ()
    • SmtpClient.SendAsync ()
    • Ping.SendAsync ()
    • WebClient.DownloadDataAsync ()
    • WebClient.DownloadFile ()
    • WebClient.DownloadFileAsync ()
    • Веб-клиент ...
    • PictureBox.LoadAsync ()
    23.09.2008

    7

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

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

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

    23.09.2008
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..