Дополнительные возможности AsyncEnumerator - Группы отказа

ОГЛАВЛЕНИЕ

Группы отказа

Другой функцией, предлагаемой классом AsyncEnumerator, являются группы отказа. Эта функция позволяет итератору выпустить несколько параллельных асинхронных операций и затем решить, что его не интересуют некоторые (или все) из них, заставляя объект AsyncEnumerator автоматически отказываться от результатов при завершении остающихся операций.

Для примера, представьте себе код, который желает получить температуру в городе. Существует много веб-служб, которым можно дать запрос на эту информацию. Можно написать итератор, запрашивающий для получения этой информации три веб-службы и как только любая из них возвратит температуру, можно заранее отказаться от результатов двух других веб-служб. Кое-кто использует этот шаблон для улучшения производительности своих приложений.

Еще один пример: итератор исполняет последовательность операций, а пользователь просто хочет отменить их, потому что ему надоело ждать или он передумал. Существует похожий случай, когда пользователь запускает асинхронную операцию, но желает отказаться ото всех операций, которые не успели завершиться за какой-то определенный промежуток времени. Например, некоторые веб-службы обрабатывают запрос клиента, и если вся обработка не может завершиться, скажем, за две секунды, служба следует уведомить клиента, что его запрос не удался, чем заставлять его ждать ответа до бесконечности.

Группы отказа используются так: внутри итератора пакет связанных операций сводится вместе как часть группы отказа. Группа отказа – это просто значение Int32 в промежутке от 0 до 63 включительно. Например, можно выпустить группу методов BeginXxx, указывая, что все они являются частью группы отказа 0. Тогда итератор сможет обработать некоторые из них по мере их завершения. Когда в коде итератора решено, что больше не следует обрабатывать операции, являющиеся частью этой группы отказа, вызывается принадлежащий AsyncEnumerator метод DiscardGroup:

public void DiscardGroup(Int32 discardGroup);

Этот метод указывает объекту AsyncEnumerator отказываться ото всех остающихся операций, созданных как часть указанной группы отказа, так что код итератора не будет видеть эти операции при вызове DequeueAsyncResult. Увы, этого не вполне достаточно, поскольку APM .NET требует, чтобы методы EndXxx вызывались для каждого метода BeginXxx, иначе возможна утечка ресурсов.

Чтобы удовлетворить это требование, AsyncEnumerator должен вызвать верный метод EndXxx для каждой операции, от которой он отказывается. Поскольку у AsyncEnumerator нет возможности самостоятельно определить верный метод EndXxx, этот метод необходимо указать. При вызове метода BeginXxx вместо простой передачи ae.End для аргумента AsyncCallback необходимо передать один из следующих методов:

AsyncCallback End(Int32 discardGroup, EndObjectXxx callback);
AsyncCallback EndVoid(Int32 discardGroup, EndVoidXxx callback);

EndObjectXxx и EndVoidXxx являются делегатами, определенными следующим образом:

delegate Object EndObjectXxx(IAsyncResult result);
delegate void EndVoidXxx(IAsyncResult result);

Если у метода BeginXxx имеется соответствующий метод EndXxx, возвращающий значение, то будет вызван только что показанный метод End. Если вызвать метод BeginXxx, имеющий соответствующий метод EndXxx, который возвращает пустоту (необычный случай), то будет вызван метод EndVoid. Теперь каждый раз, когда вы указываете AsyncEnumerator отклонить группу, он будет знать, какой метод EndXxx вызвать.

Отметьте, что если метод EndXxx выдаст какое угодно исключение, AsyncEnumerator уловит и проглотит его. Он делает это потому, что отклонение операции указывает – пользователя не волнует успех или неудача операции.

Следует также указать, что при выходе итератора или исполнения им оператора yield break, AsyncEnumerator автоматически отклоняет все группы отказа – выход из итератора указывает, что пользователя не волнуют операции, обработка которых не завершилась. Это может быть очень удобным, поскольку позволяет итератору создать какое-то число асинхронных операций, обработать столько завершенных операций, сколько ему нужно и просто выйти.
AsyncEnumerator автоматически очищает любые остающиеся операции, которые завершаются в будущем. Но обратите внимание, что отказ от операций не отменяет их. Если одна из ожидающих асинхронных операций вела запись в файл или обновляла базу данных, отказ от соответствующих групп не предотвратит завершение этих операций. Он просто позволит коду продолжать работу вне зависимости от того, завершены операции или нет.