在读取I/O卡期间触发异步事件

本文关键字:异步 事件 读取 | 更新日期: 2023-09-27 18:12:15

我正在为I/O卡创建包装器。我有一个用于访问I/O卡的DLL库。目前,我的类正在使用这个DLL来打开卡,并读取/写入它的通道。我使用while循环来连续读取输出端口,如下所示:

while (_enabled)
{
    BitArray _currentState = ReadCurrentDiValue(); // read DI data with DLL
    for (int i = 0; i < 8; i++)
    {
        if (_cardState[i] != _currentState[i])
        {
            _cardState[i] = _currentState[i];
            OnBitChanged(new BitChangedEventArgs()
            {
                Port = (byte)i,
                Value = _currentState[i]
            }); // I want to fire this event asynchronously 
        }
    }
}

这个循环在一个分离的线程上运行。我的问题是,通过在事件处理程序中执行任何困难的工作,事件侦听器可能会阻止该线程运行。在这种情况下,可能传入I/O卡的物理信号不会被识别。问题是,解决这个问题的正确方法是什么?我有两个想法。第一种方法是使用Task简单地触发事件。运行:

Task.Run(() => {
    OnBitChanged(new BitChangedEventArgs()
    {
        Port = (byte)i,
        Value = _currentState[i]
    }); 
});
第二种方法是调用委托上的BeginInvoke,在OnBitChanged方法中:
if (BitChanged != null)
{
    var listeners = BitChanged.GetInvocationList();
    foreach (var listener in listeners)
    {
        var eventHandler = (EventHandler) listener;
        eventHandler.BeginInvoke(this, eventargs, ar =>
        {
            try
            {
                eventHandler.EndInvoke(ar);
            }
            catch
            {
                // ignore
            }
        }, null);
    }
}

还有其他想法吗?我走错路了吗?

在读取I/O卡期间触发异步事件

这两种方法几乎完全相同——都将把处理程序发送到线程池。所以你最好使用Task.Run,它有一个更好的接口。

最常用的方法是使用某种类型的队列。这样,就不会浪费不必要的线程。这可能很简单,比如让另一个线程从队列中读取并执行处理程序,或者使用可以与Task.Run一起使用的自定义任务调度器。线程池本身也是一个队列,但它为处理程序引入了并行性,这可能是不希望的——我希望您的应用程序协议期望在单个线程执行中工作。

最后,如果不读取数据会导致数据丢失,即使您将处理程序发布到另一个线程,您仍然有可能丢失数据-最终,您需要一个可以自己缓冲的API。