如何同步事件处理程序

时间:2011-02-09 14:54:45

标签: c# multithreading events

多线程仍在我的待办事项列表中,因此标题可能完全错误:)

我的对象正在侦听串口,如下所示:

class MyClass
{
    MyOpticalScanner _scanner;

    public MyClass()
    {
        _scanner = new MyOpticalScanner();
        _scanner.CodeScanned += CodeScannedEventHandler;
    }

    private void CodeScannedEventHandler(object sender, CodeScannedEventArgs e)
    {
        Debug.WriteLine("ThreadID: " + Thread.CurrentThread.ManagedThreadId + " ||| " + e.ScannedCode);
        ....
        // Some code, query the database, etc...
    }
}

扫描程序正在发送有序数据:001002003004005,...但是,如果CodeScannedEventHandler中的代码处理时间太长,然后引发另一个事件,我的订单不一致。事件处理程序中的Debug.WriteLine可以给我这个:

ThreadID: 8 ||| 001
ThreadID: 9 ||| 002
ThreadID: 10 ||| 003
ThreadID: 10 ||| 006
ThreadID: 8 ||| 004
ThreadID: 8 ||| 008
ThreadID: 8 ||| 009
ThreadID: 8 ||| 010
ThreadID: 10 ||| 007
ThreadID: 9 ||| 005

我怎么能保证每个新事件只在旧事件结束后开始处理?

编辑1 - 我没有告诉你一切,实际上我在测试期间没有收听COM端口,而是创建了我自己的模拟对象。此对象(ScannerMock)使用内部System.Timer并在Timer.OnTick事件上引发CodeScanned事件。问题可能在这里吗?

来自Hans Passant's answer中的评论: SerialPort有一个内部锁,可确保DataReceived事件在运行时无法再次调用。我应该在模拟扫描程序中包含类似的锁定如何?

编辑2:我为我的扫描仪对象添加了一个锁,并将引发我的事件的代码放入其中。看起来它有效:)

5 个答案:

答案 0 :(得分:4)

如果CodeScannedEventHandler在前一个运行完毕之前再次运行,那么你会遇到一个非常大的问题。 SerialPort 执行此操作,其DataReceived事件已序列化。锁无法可靠地解决此问题,无法保证线程获取锁的顺序。你看到的是什么,Debug.WriteLine()中有一个锁。

如果序列非常重要,那么您所能做的就是尽可能保持事件处理程序的简短和快速,因此它总是比事件运行的时间花费更少的时间。将扫描结果快速存储在线程安全队列中并退出。您需要另一个清空队列的线程。它仍然不是100%的保证,你需要得到MyOpticalScanner的帮助才能得到这种保证。

答案 1 :(得分:3)

  

我怎么能保证每个新事件只在旧事件结束后开始处理?

如果这样做,则可能会丢失传入数据和/或溢出接收缓冲区。

一种方法可能是将传入的数据推送到队列中(快速),并在另一个线程中处理该队列。如果代码之间有一段时间可以工作。

但如果消费者无法跟上你仍然有问题。

答案 2 :(得分:1)

我建议你使用一个线程安全的队列类,在添加项目时,报告自从队列上次报告它是空的以来是否添加了另一个项目(将一个普通队列包装在一个锁中并添加一个'空的'旗帜 - 在锁内 - 应该足够了)。每当通信记录到达时,将其添加到队列中,如果自上次“空”报告以来没有添加任何内容,则调度一个MethodInvoker来读取并处理队列中的所有内容,并在队列为空时退出(该方法可以反复拨打“收到一条记录”事件。

如果正在处理记录而另一个记录到达,则队列将报告自上次队列报告为空时自添加了至少一条记录,因此不会调度新的MethodInvoker。在队列报告自己为空之前入队的任何记录都将由早期的MethodInvoker处理;在MethodInvoker发现队列为空之后入队的任何记录都不会被该MethodInvoker处理,并且需要启动另一个记录。

答案 3 :(得分:0)

  

我怎么能保证每个新事件只在旧事件结束后开始处理?

例如,使用lock语句:

class MyClass {
    ...
    Object myLock = new Object();
    ...

    private void CodeScannedEventHandler(object sender, CodeScannedEventArgs e)
    {
        lock(myLock) {
            ...
        }
    }
}

但请注意,这可能解决您的问题:此解决方案将阻止您的事件处理程序的两个实例并行运行。它将保证当前正在等待的线程以“正确”的顺序释放。

如果您的扫描仪组件支持此功能,那么最简单的解决方案是将该组件配置为始终在同一线程上调用事件处理程序。

答案 4 :(得分:0)

这是因为

造成的

如果您正在扫描多个线程,那么当然会发生这种情况。

您应该同步CodeScannedEventHandler方法。

例如,您可以使用lock