在事件处理程序中防止InvokeRequired的必要性

时间:2014-10-13 15:06:24

标签: c# multithreading winforms

我一直在编写一个便于与串口通信的API。我正在进行一些重构和一般清理工作,并且想知道是否有办法避免以下问题。

API中的主类能够持续读取端口,并在读取字节与特定正则表达式匹配时引发包含值的事件。读取和解析的过程发生在另一个线程上。该事件包含作为参数的值(string),并且因为它是从另一个线程引发的,所以尝试直接的客户端将值分配给,例如{{除非处理程序具有正确的Text代码,否则控件的属性会导致跨线程异常。

我理解为什么会发生这种情况,当我在我的测试客户端的事件处理程序中放入适当的调用代码时,一切都很好;我的问题是,我是否可以在API代码中做任何事情,以便客户不必担心它。

基本上,我想转此:

Invoke

简单地说:

void PortAdapter_ValueChanged(Command command, string value)
{
  if (this.InvokeRequired)
  {
    Invoke(new MethodInvoker(() =>
      {
        receivedTextBox.Text = value;
      }));
  }
  else
  {
    receivedTextBox.Text = value;
  }
}

3 个答案:

答案 0 :(得分:2)

在.Net框架本身中使用了许多地方的共同模式。例如,BackgroundWorker使用此模型。

为此,您将SynchronizationContext作为API的参数,在这种情况下,我假设它是PortAdapter

举起活动时,您可以使用SynchronizationContextSynchronizationContext.Post在给定的SynchronizationContext.Send内举起活动。前者是异步的,后者是同步的。

因此,当客户端代码创建PortAdapter的实例时,它会将WindowsFormsSynchronizationContext实例作为参数传递。这意味着PortAdapter会在给定的同步上下文中引发事件,这也意味着您不需要InvokeRequiredInvoke来电。

public class PortAdapter
{
    public event EventHandler SomethingHappened;

    private readonly SynchronizationContext context;
    public PortAdapter(SynchronizationContext context)
    {
        this.context = context ?? new SynchronizationContext();//If no context use thread pool
    }

    private void DoSomethingInteresting()
    {
        //Do something

        EventHandler handler = SomethingHappened;
        if (handler != null)
        {
            //Raise the event in client's context so that client doesn't needs Invoke
            context.Post(x => handler(this, EventArgs.Empty), null);
        }
    }
}

客户代码:

PortAdapter adpater = new PortAdapter(SynchronizationContext.Current);
...

在UI线程中创建PortAdapter的实例非常重要,否则SynchronizationContext.Current将为null,因此仍会在ThreadPool线程中引发事件。

More about SynchronizationContext here.

答案 1 :(得分:1)

TBH,检查InvokeRequired的方法很好而且灵活。

但如果您愿意,可以让您的应用程序中的所有事件都安全。为此,要么所有类都必须具有调用控件已注册

public class SomeClassWithEvent
{
    private static Control _invoke = null;

    public static void SetInvoke(Control control)
    {
        _invoke = control;
    }

    public event Action SomeEvent;
    public OnSomeEvent()
    {
        // this event will be invoked in UI thread
        if (_invoke != null && _invoke.IsHandleCreated && SomeEvent != null)
            _invoke.BeginInvoke(SomeEvent);
    }
}

// somewhere you have to register
SomeClassWithEvent.SetInvoke(mainWindow);

// and mayhaps unregister
SomeClassWithEvent.SetInvoke(null);

或暴露了该调用控件,例如:

// application class
public static class App
{
    // will be set by main window and will be used even risers to invoke event
    public static MainWindow {get; set;}
}

如果在没有创建句柄或控制注册时发生事件,您将遇到困难。

答案 2 :(得分:0)

您可以在UI线程中触发事件,这样事件处理程序(如果有)将已经在UI线程中。

public class PortAdapter
{
    public event EventHandler<string> ValueChanged;

    protected virtual void OnValueChanged(string e)
    {
        var handler = ValueChanged;
        if (handler != null)
        {
            RunInUiThread(() => handler(this, e));
        }
    }

    private void RunInUiThread(Action action)
    {
        if (InvokeRequired)
        {
            Invoke(action);
        }
        else
        {
            action.Invoke();
        }
    }
}

然而,这不是好设计,因为您不知道处理程序是否会执行UI交互。