簡體   English   中英

如何同步事件處理程序

[英]How to synchronize event handlers

多線程仍然在我的工作清單上,因此標題可能是完全錯誤的:)

我的對象正在偵聽串行端口,如下所示:

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的答案中的注釋中可以看出SerialPort具有一個內部鎖,該鎖可確保DataReceived事件在運行時不會再次被調用。 我應該在模擬掃描儀中包括類似的鎖嗎?

編輯2:我向掃描儀對象添加了一個鎖,並將引發事件的代碼放入其中。 看起來很有效:)

如果CodeScannedEventHandler在上一個完成運行之前再次運行,則會遇到很大的問題。 串行端口執行此操作,它的DataReceived事件已序列化。 鎖不能可靠地解決此問題,不能保證線程獲得鎖的順序。 這就是您所看到的情況,Debug.WriteLine()中有一個鎖。

如果順序確實很重要,那么您所能做的就是使事件處理程序盡可能短且活潑,因此它總是比事件運行的時間花費更少的時間。 快速將掃描結果存儲在線程安全的隊列中並退出。 您需要另一個清空隊列的線程。 它仍然不是100%的保證,您需要得到MyOpticalScanner撰寫者的幫助才能獲得該保證。

我如何確保每個新事件僅在舊事件結束后才開始處理?

如果這樣做,則可能會丟失輸入數據和/或接收緩沖區溢出。

一種方法可能是將傳入數據(快速)推送到隊列中,然后在另一個線程中處理該隊列。 如果在這兩個代碼之間有一段時間會起作用。

但是,如果消費者跟不上您,您仍然會遇到麻煩。

我建議您使用線程安全隊列類,該類在添加項目時報告自隊列上次報告其為空以來是否添加了其他項目(將普通隊列包裝在鎖中並添加“空”標志) -在鎖內-應該足夠)。 每當通信記錄到達時,將其添加到隊列中,如果自上次“空”報告以來未添加任何內容,則調度MethodInvoker讀取並處理隊列中的所有內容,並在隊列為空時退出(該方法可以重復調用“收到一個記錄”事件)。

如果在另一條記錄到達時正在處理一條記錄,則隊列將報告自隊列上次報告自身為空以來已添加了至少一條記錄,因此將不會分派新的MethodInvoker。 在隊列報告自身為空之前排隊的任何記錄將由較早的MethodInvoker處理; 在MethodInvoker發現隊列為空之后排隊的任何記錄都不會由該MethodInvoker處理,並且需要啟動另一個記錄。

我如何確保每個新事件僅在舊事件結束后才開始處理?

例如,通過使用lock語句:

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

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

但是請注意,這可能不是解決你的問題:該解決方案將防止並行運行的事件處理程序的兩個實例。 不能保證當前正在等待的線程以“正確”的順序釋放。

如果您的掃描程序組件支持此功能,那么最簡單的解決方案是將該組件配置為始終在同一線程上調用事件處理程序。

這是因為

如果您要掃描多個線程,那么這當然會發生。

您應該同步CodeScannedEventHandler方法。

您可以使用例如lock

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM