我一直在编写一个便于与串口通信的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;
}
}
答案 0 :(得分:2)
在.Net框架本身中使用了许多地方的共同模式。例如,BackgroundWorker
使用此模型。
为此,您将SynchronizationContext
作为API的参数,在这种情况下,我假设它是PortAdapter
。
举起活动时,您可以使用SynchronizationContext
或SynchronizationContext.Post
在给定的SynchronizationContext.Send
内举起活动。前者是异步的,后者是同步的。
因此,当客户端代码创建PortAdapter
的实例时,它会将WindowsFormsSynchronizationContext
实例作为参数传递。这意味着PortAdapter
会在给定的同步上下文中引发事件,这也意味着您不需要InvokeRequired
或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.Current
将为null,因此仍会在ThreadPool线程中引发事件。
答案 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交互。