2011-02-09 4 views
1

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

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

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. Может ли проблема быть здесь?

От комментария Hans Passant's answer: У SerialPort есть внутренний замок, который гарантирует, что событие DataReceived не может быть вызвано во время его работы. Должен ли я включить аналогичную блокировку в мой издевательский сканер и как?

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

+1

Почему вы обрабатываете значение в нескольких потоках, то? В любом случае вы всегда можете заблокировать его в сканере, чтобы он запускал только одно событие за раз. –

+0

Я не думаю, что проблема связана с обработчиками событий, поскольку они не являются асинхронными. Метод «scan» - это тот, который выполняется асинхронно, и поскольку обработчик событий приходит из него, похоже, что они также асинхронны, но они НЕ. – Jaider

ответ

4

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

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

+0

Я не уверен, понимаю ли я это. Сканер поднимает событие, когда он сканирует новый код (около 10 в секунду, всегда с той же частотой). Обычно мой код достаточно быстрый, но каждые 100 сканирований я должен искать в базе данных и делать некоторые вставки и другие вещи. Я попробую решение с очередью. Кроме того - я был тем, кто написал MyOpticalScanner, это всего лишь оболочка объекта Syste.IO.Port с некоторыми дополнительными знаниями. – sventevit

+0

Что значит, что событие DataReceived сериализовано? – sventevit

+1

SerialPort имеет внутреннюю блокировку, которая гарантирует, что событие DataReceived не может быть вызвано снова во время его работы. Если вы сделаете это, генерируете событие CodeScannedEventHandler напрямую, ваша проблема будет решена. –

3

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

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

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

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

0

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

Например, с помощью lock заявление:

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

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

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

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

0

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

При сканировании более чем в одном потоке это будет происходить, конечно.

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

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

1

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

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

Смежные вопросы