如何使用多线程.NET C#进行正确的封装
本文关键字:封装 何使用 多线程 NET | 更新日期: 2023-09-27 18:21:02
如您所见,我有两个类。RfidReaderHardware在线程"th"中生成事件,但Form在另一个线程上运行。如您所见,在表单中,如果使用ListViewControl的Invoke方法。所以,问题是如何改变RfidReaderHardware来解决封装问题。
public class RfidReaderHardware : IDisposable
{
public event EventHandler<RfidReaderEventArgs> OnNewPackage;
Thread th;
//This method will be called from thread "th"
private void FireNewPackageEvent(UHFPackage package)
{
... code ...
}
... some code ...
}
我们有示例代码,其中该事件使用
public partial class PassageForm : Form
{
RfidReaderHardware RfidReader = new RfidReaderHardware(...);
private void Form1_Load(object sender, EventArgs e)
{
RfidReader.OnNewPackage += NewRfidPackage;
}
//not sure, but i think it's running in thread "th"
private void NewRfidPackage(Object o, RfidReaderEventArgs e)
{
ListViewItem item = new ListViewItem();
//from point of encapsulation view it's wrong as you know
CPackageList.Invoke(new Action(() => {CPackageList.Items.Add(item); }));
}
}
问题是如何更改RfidReaderHardware以解决封装问题
事实上不存在封装问题。根据定义,事件源和订阅者之间的关系是一对多的,因此源不能"封装"特定订阅者的逻辑。用户可以选择如何处理通知。可以忽略它,也可以立即处理它,或者像您的情况一样,在UI线程上同步(使用Control.Invoke
)或异步(使用Control.BeginInvoke
)处理它。
不确定是否真的需要解决这个问题,让UI对象本身处理在"错误"线程上触发事件这一事实并不是一个缺陷。只要你知道它实际上是在错误的线程上启动的,这是一个文档要求。
然而,.NET有一个通用的机制来解决这个问题,它在.NET Framework代码中的几个地方都有使用。RfidReaderHardware类构造函数可以复制SynchronizationContext.Current的值并将其存储在字段中。隐含地假设对象是由UI线程上运行的代码创建的。当您准备好激发事件,并且复制的对象不是null时,您可以使用它的Post()或Send()方法。这自动使代码在UI线程上恢复。不管使用了什么特定的UI类库,例如在WPF或Universal应用程序中都能很好地工作。
一些示例代码,不需要太多:
public class RfidReaderHardware {
public event EventHandler Received;
public RfidReaderHardware() {
syncContext = System.Threading.SynchronizationContext.Current;
}
protected void OnReceived(EventArgs e) {
if (syncContext == null) FireReceived(e);
else syncContext.Send((_) => FireReceived(e), null);
}
protected void FireReceived(EventArgs e) {
var handler = Received;
if (handler != null) Received(this, e);
}
private System.Threading.SynchronizationContext syncContext;
}