C# 防止事件处理程序中需要调用

C# 防止事件处理程序中需要调用,c#,multithreading,winforms,C#,Multithreading,Winforms,我一直在写一个API,它可以促进与串行端口的通信。我正在做一些重构和常规清理,想知道是否有办法避免以下问题 API中的主类能够不断从端口读取数据,并在读取字节与特定正则表达式匹配时引发包含值的事件。读取和解析过程发生在另一个线程上。该事件将该值作为参数(string)包含,并且由于它是从另一个线程引发的,因此试图直接将该值分配给控件的Text属性的客户端会导致跨线程异常,除非处理程序具有正确的调用代码 我理解为什么会发生这种情况,当我在测试客户机的事件处理程序中放入适当的调用代码时,一切都很好;

我一直在写一个API,它可以促进与串行端口的通信。我正在做一些重构和常规清理,想知道是否有办法避免以下问题

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

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

从本质上讲,我想把这一点:

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

void PortAdapter_ValueChanged(Command command, string value)
{
    receivedTextBox.Text = value;
}

您可以在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();
        }
    }
}
公共类PortAdapter
{
公共事件处理程序值已更改;
受保护的虚拟无效OnValueChanged(字符串e)
{
var handler=ValueChanged;
if(处理程序!=null)
{
RunInUiThread(()=>handler(this,e));
}
}
私有void RunInUiThread(操作)
{
如果(需要调用)
{
援引(行动);
}
其他的
{
action.Invoke();
}
}
}

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

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

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

或者将该调用控件公开,例如:

// 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;}
}

如果在未创建句柄或未注册控件的情况下发生事件,您将遇到困难。

在.Net framework本身的许多地方都使用了一种常见的模式。例如,
BackgroundWorker
使用此模型

为此,您将使用
SynchronizationContext
作为API的参数,在本例中,我假定它是
PortAdapter

引发事件时,您可以使用
SynchronizationContext.Post
SynchronizationContext.Send
在给定的
SynchronizationContext
中引发事件。前者是异步的,后者是同步的

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

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。当前的
将为null,因此线程池线程中仍会引发事件


几乎同时得到3个答案很有趣,但在半小时的无所事事后=D
PortAdapter:UserControl
?真讨厌。这是一个用户界面,现在已删除
UserControl
,不会编译为
invokererequired
Invoke
未知:(复制SynchronizationContext。当前是样板方法。此选项似乎对代码库的影响最小(我真的不想让我的类继承自
UserControl
,只是为了让它正常工作)。起初,我认为与调用
Invoke
相比,必须在上下文中传递是一种折衷,但由于它是构造函数的一个参数,至少现在它是强制性的,并且在文档中更容易解释。谢谢!
PortAdapter adpater = new PortAdapter(SynchronizationContext.Current);
...