Самый чистый способ для вызова сквозных событий

Я нахожу, что модель событий .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
}

Ответ 1

Несколько наблюдений:

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

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

Ответ 2

У меня некоторый код для этого в Интернете. Это намного лучше других предложений; определенно проверьте это.

Использование образца:

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

Ответ 3

Я избегаю избыточных объявлений делегатов.

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.

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

Ответ 4

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

Ответ 5

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

В XAML:

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

Ответ 6

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

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 ​​в основной форме.

Ответ 7

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

    #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());

        }
    }

Ответ 8

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

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

Ответ 9

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