C# 防止事件处理程序中需要调用
我一直在写一个API,它可以促进与串行端口的通信。我正在做一些重构和常规清理,想知道是否有办法避免以下问题 API中的主类能够不断从端口读取数据,并在读取字节与特定正则表达式匹配时引发包含值的事件。读取和解析过程发生在另一个线程上。该事件将该值作为参数(C# 防止事件处理程序中需要调用,c#,multithreading,winforms,C#,Multithreading,Winforms,我一直在写一个API,它可以促进与串行端口的通信。我正在做一些重构和常规清理,想知道是否有办法避免以下问题 API中的主类能够不断从端口读取数据,并在读取字节与特定正则表达式匹配时引发包含值的事件。读取和解析过程发生在另一个线程上。该事件将该值作为参数(string)包含,并且由于它是从另一个线程引发的,因此试图直接将该值分配给控件的Text属性的客户端会导致跨线程异常,除非处理程序具有正确的调用代码 我理解为什么会发生这种情况,当我在测试客户机的事件处理程序中放入适当的调用代码时,一切都很好;
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个答案很有趣,但在半小时的无所事事后=DPortAdapter:UserControl
?真讨厌。这是一个用户界面,现在已删除UserControl
,不会编译为invokererequired
和Invoke
未知:(复制SynchronizationContext。当前是样板方法。此选项似乎对代码库的影响最小(我真的不想让我的类继承自UserControl
,只是为了让它正常工作)。起初,我认为与调用Invoke
相比,必须在上下文中传递是一种折衷,但由于它是构造函数的一个参数,至少现在它是强制性的,并且在文档中更容易解释。谢谢!
PortAdapter adpater = new PortAdapter(SynchronizationContext.Current);
...