В текущей реализации Main() интервал времени между вызовами BeginInvoke() и EndInvoke() составляет явно менее пяти секунд. Поэтому после вывода на консоль
сообщения Doing more work in Main()! вызывающий поток теперь будет блокироваться и ожидать завершения работы вторичным потоком, прежде чем получить результат метода Add (). Из этого следует, что, по сути, выполняется еще один синхронный вызов:
static void Main(string[] args)
{
BinaryOp b = new BmaryOp (Add);
IAsyncResult iftAR = b.Beginlnvoke (10, 10, null, null);
// Выполнение этого вызова будет занимать менее пяти секунд!
Console.WriteLine("Doing more work in Main()!");
Console.WriteLine("Выполнение еще кое-какой работы в Main()!");
// Вызывающий поток теперь из-за этого будет блокироваться до тех пор,
// пока метод Beginlnvoke() не завершит свою работу.
int answer = b.Endlnvoke(iftAR);
}
Очевидно, что асинхронные делегаты теряют свою привлекательность, если возникает вероятность блокирования вызывающего потока при тех или иных обстоятельствах. Для предоставления вызывающему потоку возможности выяснить, завершил ли метод, вызывавшийся асинхронным образом, свою работу, интерфейс IAsyncResult предлагает свойство IsCompleted. С его помощью вызывающий поток может определить, был ли действительно завершен асинхронный вызов, перед тем как вызывать метод Endlnvoke(). Если он не был завершен, IsCompleted вернет false и позволит вызывающему потоку продолжать свою работу, а если был завершен, IsCompleted вернет true и даст возможность вызывающему потоку получить результат "наименее
блокирующим" образом. Давайте модифицируем метод Main(), как показано ниже:
static void Main(string[] args)
{
…
BinaryOp b = new BinaryOp(Add);
IAsyncResult iftAR = b.BeginInvoke(10, 10, null, null);
// Это сообщение будет выводиться до тех пор,
// пока не завершится выполнение метода Add().
while(!iftAR.IsCompleted)
{
Console.WriteLine("Doing more work in Main()!");
Thread.Sleep(1000);
}
// Теперь известно, что выполнение метода Add() завершилось.
int answer = b.EndInvoke(iftAR);
...
Здесь мы входим в цикл, в котором оператор Console.WriteLine() продолжает обрабатываться до тех пор, пока вторичный поток не завершит свою работу. Как только
это происходит, можно получить результат метода Add(), поскольку точно известно, что
выполнение метода действительно закончилось. Вызов Thread.Sleep(1000) является
совершенно не обязательным для корректного функционирования данного приложения;
однако принуждение главного потока ждать по приблизительно одной секунде при
каждой итерации позволяет избегать отображения одного и того же сообщения сотни раз.
Помимо свойства IsCompleted интерфейс IAsyncResult еще предоставляет свойство AsyncWaitHandle для построения более гибкой логики ожидания. Это свойство возвращает экземпляр типа WaitHandle, который предоставляет доступ к методу
WaitOne (). Преимущество метода WaitHandle.WaitOne() состоит в том, что он позволяет задавать максимальное время ожидания. В случае превышения указанного количества времени WaitOne () возвращает false. Ниже приведена обновленная версия цикла
while, в котором больше не используется вызов Thread.Sleep():
while (!iftAR.AsyncWaitHandle.WaitOne (1000, true))
{
Console.WriteLine("Doing more work in Main()!");
}
Хотя описанные свойства IAsyncResult действительно предоставляют способ для
синхронизации вызывающего потока, самым эффективным подходом они не являются.
Во многих отношениях свойство IsCompleted напоминает назойливого менеджера, который постоянно спрашивает, сделана уже работа или нет. К счастью, делегаты
поддерживают ряд дополнительных (и более элегантных) методик для получения результата метода, который был вызван асинхронным образом.