Самый чистый способ вызвать межпоточные события

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

Основываясь на предложениях сообщества, я использовал это:

// earlier in the code
mCoolObject.CoolEvent+= 
           new CoolObjectEventHandler(mCoolObject_CoolEvent);
// then
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        CoolObjectEventHandler cb =
            new CoolObjectEventHandler(
                mCoolObject_CoolEvent);
        Invoke(cb, new object[] { sender, args });
        return;
    }
    // do the dirty work of my method here
}

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

GregC 29.04.2009 21:30
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
79
1
117 770
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Я избегаю повторяющихся деклараций делегатов.

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
        return;
    }
    // do the dirty work of my method here
}

Для не-событий вы можете использовать делегат System.Windows.Forms.MethodInvoker или System.Action.

Обновлено: Кроме того, у каждого события есть соответствующий делегат EventHandler, поэтому нет необходимости повторно объявлять его.

Для меня это работало так: Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);

António Almeida 18.08.2015 19:28

@ToniAlmeida Да, это была опечатка в моем коде. Спасибо, что указали на это.

Konrad Rudolph 18.08.2015 20:07

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

private void OnCoolEvent(CoolObjectEventArgs e)
{
  BeginInvoke((o,e) => /*do work here*/,this, e);
}

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

supercat 18.04.2011 02:02
Ответ принят как подходящий

Пара наблюдений:

  • Не создавайте простые делегаты явно в таком коде, если вы не до версии 2.0, поэтому вы могли бы использовать:
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • Также вам не нужно создавать и заполнять массив объектов, потому что параметр args является типом params, поэтому вы можете просто передать его в список.

  • Я, вероятно, предпочел бы Invoke, а не BeginInvoke, поскольку последний приведет к асинхронному вызову кода, который может быть или не быть тем, что вам нужно, но затруднит распространение обработки последующих исключений без вызова EndInvoke. Что может случиться, так это то, что ваше приложение получит вместо этого TargetInvocationException.

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

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

В XAML:

<TextBox Text = "{Binding Path=Name}"/>

это не сработает. как только вы установите опору в потоке без пользовательского интерфейса, вы получите исключение .. т.е. Name = "gbc" bang! неудача ... нет бесплатного сырного товарища

Boppity Bop 23.11.2011 15:40

Это не бесплатно (требует времени выполнения), но механизм связывания wpf, похоже, автоматически обрабатывает межпоточный маршаллинг. Мы часто используем это с реквизитами, которые обновляются сетевыми данными, полученными в фоновых потоках. Здесь есть объяснение: blog.lab49.com/archives/1166

gbc 28.11.2011 01:43

@gbc Aaaaand объяснение исчезло 404.

Jan 'splite' K. 11.09.2017 12:23

У меня какой-то код для этого в сети. Это намного лучше, чем другие предложения; обязательно зацените.

Пример использования:

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    // You could use "() =>" in place of "delegate"; it's a style choice.
    this.Invoke(delegate
    {
        // Do the dirty work of my method here.
    });
}

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

Joe Almore 02.02.2015 15:43

Я думаю, что самый чистый способ - это определенно, чтобы пойти по маршруту АОП. Сделайте несколько аспектов, добавьте необходимые атрибуты, и вам больше не придется проверять сходство потоков.

Я не понимаю твоего предложения. C# не является изначально аспектно-ориентированным языком. Вы имеете в виду какой-то шаблон или библиотеку для реализации аспектов, которые негласно реализуют маршалинг?

Eric 05.05.2009 22:08

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

Dmitri Nesteruk 06.05.2009 09:59

Я сделал следующий универсальный класс вызовов перекрестных потоков для своих целей, но думаю, что им стоит поделиться:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace CrossThreadCalls
{
  public static class clsCrossThreadCalls
  {
    private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value);
    public static void SetAnyProperty(Control c, string Property, object Value)
    {
      if (c.GetType().GetProperty(Property) != null)
      {
        //The given property exists
        if (c.InvokeRequired)
        {
          SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty);
          c.BeginInvoke(d, c, Property, Value);
        }
        else
        {
          c.GetType().GetProperty(Property).SetValue(c, Value, null);
        }
      }
    }

    private delegate void SetTextPropertyCallBack(Control c, string Value);
    public static void SetTextProperty(Control c, string Value)
    {
      if (c.InvokeRequired)
      {
        SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty);
        c.BeginInvoke(d, c, Value);
      }
      else
      {
        c.Text = Value;
      }
    }
  }

И вы можете просто использовать SetAnyProperty () из другого потока:

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());

В этом примере вышеупомянутый класс KvaserCanReader запускает свой собственный поток и выполняет вызов для установки текстового свойства метки lb_Speed ​​в основной форме.

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

    #region SyncContextCancel

    private SynchronizationContext _syncContextCancel;

    /// <summary>
    /// Gets the synchronization context used for UI-related operations.
    /// </summary>
    /// <value>The synchronization context.</value>
    protected SynchronizationContext SyncContextCancel
    {
        get { return _syncContextCancel; }
    }

    #endregion //SyncContextCancel

    public void CancelCurrentDbCommand()
    {
        _syncContextCancel = SynchronizationContext.Current;

        //ThreadPool.QueueUserWorkItem(CancelWork, null);

        Thread worker = new Thread(new ThreadStart(CancelWork));
        worker.Priority = ThreadPriority.Highest;
        worker.Start();
    }

    SQLiteConnection _connection;
    private void CancelWork()//object state
    {
        bool success = false;

        try
        {
            if (_connection != null)
            {
                log.Debug("call cancel");
                _connection.Cancel();
                log.Debug("cancel complete");
                _connection.Close();
                log.Debug("close complete");
                success = true;
                log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
            }
        }
        catch (Exception ex)
        {
            log.Error(ex.Message, ex);
        }

        SyncContextCancel.Send(CancelCompleted, new object[] { success });
    }

    public void CancelCompleted(object state)
    {
        object[] args = (object[])state;
        bool success = (bool)args[0];

        if (success)
        {
            log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());

        }
    }

Другие вопросы по теме