andrzejn: (Curious)
Андрій Новосьолов ([personal profile] andrzejn) wrote2007-07-17 09:20 pm

Когда SyncLock не помогает

В .NET Framework есть аналог synchronized блоков в Java - в VB.NET это блок SyncLock, а в C# он называется lock. Он гарантирует, что несколько потоков одновременно не будут исполнять код внутри блока - пока один из потоков находится внутри, остальные претенденты ждут своей очереди.

Но есть ситуация, в которой SyncLock не работает.

Делаем такую конструкцию:

1. Метод, который всегда вызывается только в основном (интерфейсном) потоке. Например, обработчик тиков Windows.Forms.Timer или просто метод, который другие потоки вызывают через Control.Invoke.

2. В теле метода размещаем блок SyncLock.

3. Внутри метода, среди прочего, делаем что-нибудь длительное, вызывая в промежутках Application.DoEvents.

4. Обеспечиваем нагрузку на метод - если он таймерный, то тики должны приходить достаточно часто, если он вызывается из других потоков через Invoke, то вызываться он должен достаточно часто, чтобы несколько запросов иногда скапливались в очереди.

И тогда мы обнаружим, что несколько точек исполнения попадают вовнутрь SyncLock одновременно.

Как это получается?

Одной фразой проблема объясняется так: SyncLock обеспечивает блокировку разных потоков, но не рекурсивных вызовов в одном и том же потоке.

И Forms.Timer, и Control.Invoke работают через очередь сообщений Windows. Да, ту самую очередь сообщений, которая тянется ещё с ранних 16-битных версий Windows. Пока одно сообщение обрабатывается, остальные ждут в очереди.

Вызов Application.DoEvents - это требование разобрать и обработать сообщения, накопившиеся в очереди. Пока вся очередь не разберётся, DoEvents не вернёт управление. Разбор очереди происходит в том же самом потоке управления, откуда вызывали DoEvents.

Если одним из сообщений в очереди является требование вызвать наш метод, то получается рекурсивный вызов: метод A вызывает DoEvents, который вызывает метод A. И вот мы снова вошли в наш метод - и SyncLock не заблокировал второй вход (и хорошо, что не заблокировал, а то бы тут всё и зависло). Но если вы такого не ожидали, можете нарваться на неприятный сюрприз.

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

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
No Subject Icon Selected
More info about formatting