Подтвердить что ты не робот

С# Многопоточность - Вызов без управления

Я только немного знаком с многопоточными в том, что я читал об этом, но никогда не использовал его на практике.

У меня есть проект, который использует стороннюю библиотеку, которая разделяет статус устройства ввода, поднимая события. Проблема в том, как записывается библиотека, эти события создаются из другого потока.

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

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

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

Проблема заключается в том, что класс, получающий эти входные события, не выводится из Control и поэтому не имеет InvokeRequired или BeginInvoke. Причиной этого является то, что я попытался четко разделить интерфейс и основную логику. Класс все еще запущен в потоке пользовательского интерфейса, он просто не имеет интерфейса внутри самого класса.

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

Может быть, есть способ сохранить ссылку на поток, который запускал конструктор, а затем что-то в пространстве имен Threading, которое будет выполнять команды Invoke?

Есть ли способ обойти это? Является ли мой подход совершенно неправильным?

4b9b3361

Ответ 1

Посмотрите на класс AsyncOperation. Вы создаете экземпляр AsyncOperation в потоке, который вы хотите вызвать обработчиком, используя метод AsyncOperationManager.CreateOperation. Аргумент, который я использую для Create, обычно имеет значение null, но вы можете установить его на что угодно. Чтобы вызвать метод в этом потоке, используйте метод AsyncOperation.Post.

Ответ 2

Используйте SynchronizationContext.Current, который укажет на то, с чем вы можете синхронизировать.

Это сделает правильную вещь ™ в зависимости от типа приложения. Для приложения WinForms он будет запускаться в основном потоке пользовательского интерфейса.

В частности, используйте метод SynchronizationContext.Send, например:

SynchronizationContext context =
    SynchronizationContext.Current ?? new SynchronizationContext();

context.Send(s =>
    {
        // your code here
    }, null);

Ответ 3

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

Опять же, нужно активировать только те элементы управления пользовательского интерфейса, которые вы хотите обновить, чтобы сделать их потокобезопасными. Некоторое время назад я написал запись в блоге "" Простое решение для незаконных вызовов в потоке "на С#"

Сообщение идет более подробно, но суть очень простого (но ограниченного) подхода заключается в использовании анонимной функции делегата в элементе управления UI, который вы хотите обновить:

if (label1.InvokeRequired) {
  label1.Invoke(
    new ThreadStart(delegate {
      label1.Text = "some text changed from some thread";
    }));
} else {
  label1.Text = "some text changed from the form thread";
}

Надеюсь, это поможет. InvokeRequired является технически необязательным, но элементы управления Invoke довольно дороги, поэтому убедитесь, что он не обновляет label1.Text через invoke, если он не нужен.

Ответ 4

Вам не нужен конкретный элемент управления, любой элемент управления (включая форму). Таким образом, вы можете немного отвлечь его от пользовательского интерфейса.

Ответ 5

Если вы используете WPF:

Вам нужна ссылка на объект Dispatcher, который управляет потоком пользовательского интерфейса. Затем вы можете использовать метод Invoke или BeginInvoke в объекте диспетчера для планирования операции, которая происходит в потоке пользовательского интерфейса.

Самый простой способ получить диспетчера - использовать Application.Current.Dispatcher. Это диспетчер, ответственный за основной (и, возможно, единственный) поток пользовательского интерфейса.

Объединяя все это:

class MyClass
{
    // Can be called on any thread
    public ReceiveLibraryEvent(RoutedEventArgs e)
    {
        if (Application.Current.CheckAccess())
        {
            this.ReceiveLibraryEventInternal(e);
        }
        else
        {
            Application.Current.Dispatcher.Invoke(
                new Action<RoutedEventArgs>(this.ReceiveLibraryEventInternal));
        }
    }

    // Must be called on the UI thread
    private ReceiveLibraryEventInternal(RoutedEventArgs e)
    {
         // Handle event
    }
}

Ответ 6

Есть ли способ обойти это?

Да, для работы над созданием потокобезопасной очереди будет работать.

  • Обработчик событий вызывается сторонним потоком
  • Обработчик событий помещает что-то (данные события) в коллекцию (например, Список), которой вы владеете
  • Ваш обработчик событий делает что-то, чтобы сигнализировать вашему собственному ада, что в коллекции собираются данные для его удаления и обработки:
    • Ваша нить может ждать чего-то (мьютекса или что-то еще); когда его мьютекс сигнализируется обработчиком события, он просыпается и проверяет очередь.
    • В качестве альтернативы, вместо того, чтобы сигнализировать, он может периодически просыпаться (например, один раз в секунду или что-то еще) и опросить очередь.

В любом случае, потому что ваша очередь записывается двумя разными потоками (поток сторонних сторон помещается в очередь, а ваш поток отключается), он должен быть потокобезопасной защищенной очередью.