如何使用多线程.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); }));
    }
}

如何使用多线程.NET C#进行正确的封装

问题是如何更改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;
}