Как синхронизировать обработчики событий - PullRequest
1 голос
/ 09 февраля 2011

Многопоточность все еще в моем списке дел, поэтому заголовок может быть совершенно неверным:)

Мой объект прослушивает последовательный порт, например:

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...
    }
}

Сканеротправляет упорядоченные данные: 001, 002, 003, 004, 005, ... Но если код в 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 и вызывает событие CodeScanned для события Timer.OnTick.Может ли проблема быть здесь?

Из комментария в Ответ Ханса Пассанта : SerialPort имеет внутреннюю блокировку, которая гарантирует, что событие DataReceived не может быть вызвано снова во время работы. Должен ли я включить аналогичную блокировку в свой сканерный сканер и как?

Редактировать 2: Я добавил блокировку к своему объекту сканера и вставил в него код, который вызывает мое событие.Похоже, это работает:)

Ответы [ 5 ]

4 голосов
/ 09 февраля 2011

У вас довольно большая проблема, если CodeScannedEventHandler снова запустится до того, как предыдущий завершит работу. SerialPort не делает это, кстати, его событие DataReceived сериализуется. Блокировка не может надежно решить эту проблему, порядок, в котором потоки получают блокировку, не гарантируется. Что и происходит, в Debug.WriteLine () есть блокировка.

Если последовательность действительно важна, то все, что вы можете сделать, это сделать обработчик событий как можно более коротким и быстрым, чтобы он всегда занимал меньше времени, чем скорость, с которой запускаются события. Быстро сохраните результат сканирования в поточно-ориентированной очереди и выйдите. Вам нужен другой поток, который очищает очередь. Это все еще не 100% гарантия, вам понадобится помощь того, кто написал MyOpticalScanner, чтобы получить эту гарантию.

3 голосов
/ 09 февраля 2011

Как я могу убедиться, что каждое новое событие начинает обрабатываться только после завершения старого?

Если вы это сделаете, вы рискуете потерять входящие данные и / или переполнить приемные буферы.

Один из подходов может заключаться в том, чтобы помещать входящие данные в очередь (быстро) и обрабатывать эту очередь в другом потоке. Если между кодами будет какое-то время, это сработает.

Но если потребитель не поспевает, у вас все еще проблемы.

1 голос
/ 10 февраля 2011

Я бы посоветовал вам использовать потокобезопасный класс очереди, который сообщает при добавлении элемента, был ли добавлен другой элемент с тех пор, как очередь в последний раз сообщала, что он пуст (оборачивая обычную очередь в блокировку и добавляя ' пустой флаг - в пределах блокировки - должно хватить). Каждый раз, когда поступает коммуникационная запись, добавьте ее в очередь и, если ничего не было добавлено со времени последнего «пустого» отчета, отправьте MethodInvoker для считывания и обработки всего в очереди и выхода, когда очередь пуста (этот метод может повторно вызывать событие «одна запись получена»).

Если запись обрабатывается при поступлении другой, очередь сообщит, что по крайней мере одна запись была добавлена ​​с момента последнего сообщения о том, что очередь пуста, поэтому новый MethodInvoker отправляться не будет. Любая запись, которая ставится в очередь до того, как очередь сообщает, что она пуста, будет обработана более ранним MethodInvoker; любая запись, которая ставится в очередь после того, как MethodInvoker обнаружит, что очередь пуста, не будет обрабатываться этим MethodInvoker и потребует запуска другой.

0 голосов
/ 09 февраля 2011

Это вызвано тем, что

Если вы сканируете более одного потока, это, конечно, произойдет.

Вы должны синхронизировать метод CodeScannedEventHandler.

Вы можете использовать lock например.

0 голосов
/ 09 февраля 2011

Как я могу убедиться, что каждое новое событие начинает обрабатываться только после завершения старого?

Например, с помощью оператора lock:

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

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

Обратите внимание, однако, что это может не решить вашу проблему: Это решение предотвратит два экземпляра вашего обработчика событий, работающих параллельно. не гарантирует, что потоки, ожидающие в настоящий момент, будут освобождены в «правильном» порядке.

Если ваш компонент сканера поддерживает это, то самым простым решением будет настроить этот компонент навсегда вызывайте обработчик событий в одном потоке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...