Дополнительные возможности AsyncEnumerator - Синхронизация доступа к общим данным

ОГЛАВЛЕНИЕ

Синхронизация доступа к общим данным

В некоторых ситуациях, особенно относящихся к серверу, может существовать несколько объектов AsyncEnumerator (по одному на клиентский запрос), каждый из которых одновременно обрабатывает собственные итераторы. Для примера, представьте себе веб-узел, который получает доступ к некоей базе данных и затем обновляет набор объектов в памяти. Для доступа к базе данных определенно стоит использовать APM (например, вызывая BeginExecuteReader из класса SqlCommand), после чего необходимо обновить находящиеся в памяти объекты поддерживающим потоки образом. В обычной ситуации здесь можно использовать методы класса Monitor или выражение блокировки C#, или, возможно, класс ReaderWriterLockSlim, поставляемый в комплекте с .NET Framework 3.5. Однако все эти блокировки могут заблокировать вызывающий поток, вредя масштабируемости и скорости ответа. Чтобы избежать блокировки потоков, я обычно использую мой класс ReaderWriterGate, который я описал в своей статье за май 2006 года (см. msdn.microsoft.com/magazine/cc163532).

Когда я начал использовать ReaderWriterGate с AsyncEnumerator, я осознал, что объектная модель ReaderWriterGate может быть улучшена для более качественной интеграции с AsyncEnumerator. Так что я создал новый класс SyncGate, поведение которого очень похоже на ReaderWriterGate. Вот его модель:

  public sealed class SyncGate {
   public SyncGate();
   public void BeginRegion(SyncGateMode mode, 
    AsyncCallback asyncCallback); 
   public void EndRegion(IAsyncResult ar); 
  }
  public enum SyncGateMode { Exclusive, Shared }

При наличии нескольких работающих итераторов, желающих получить доступ к общим данным, сперва сконструируйте SyncGate и сохраните ссылку на него в статическом поле, либо как-нибудь направьте ссылку на него различным итераторам. Затем внутри итераторов, прямо перед кодом, касающимся общих данных, вызовите принадлежащий SyncGate метод BeginRegion, указывающий, необходим ли коду исключительный (запись) или общий (чтение) доступ к данным. Затем пусть итератор применит yield return 1. На этом этапе итератор отпустит свой поток, и когда итератор получит безопасный доступ к данным, AsyncEnumerator автоматически произведет обратный вызов к коду разработчика. Это значит, что потоки не будут блокироваться, когда они ожидают доступа к общим данным.

В итераторе, прямо после кода, касающегося общих данных, вызовите EndRegion. Это укажет SyncGate, что код закончил касаться общих данных и позволяет другим итераторам получить к ним доступ, если им это надо. На рис. 2 внизу итератора GetWebSiteDataLength используется SyncGate для исключительного доступа к статической коллекции. Также на рис. 2 итератор GetRecentRequests показывает, как получить общий доступ к той же самой коллекции.