Обработка исключений Async с void

Я использую Async CTP для написания приложения IO heavy console. Но у меня проблемы с исключениями.

public static void Main()
{
   while (true) {
     try{
         myobj.DoSomething(null);
     }
     catch(Exception){}
     Console.Write("done");
     //...
   }
}

//...
public async void DoSomething(string p)
{
   if (p==null) throw new InvalidOperationException();
   else await SomeAsyncMethod();
}

И происходит следующее: "done" записывается в консоль, затем я получаю исключение в отладчике, затем нажимаю continue моя программа существует.
Что дает?

Ответ 1

Когда вы вызываете DoSomething(), в основном создается Task под капотом и начинается с Task. Поскольку у вас есть подпись void, нет объекта Task, чтобы сигнализировать об этом или что вы могли бы заблокировать его, поэтому выполнение сразу закончилось. Тем временем задача выдает исключение, которое никто не ловит, что, я подозреваю, является причиной того, что ваша программа завершается.

Я думаю, что поведение, которое вы хотели, больше похоже на это:

public static void Main()
{
   while (true) {
     var t = myobj.DoSomething(null);
     t.Wait();
     if(t.HasException) {
       break;
     }
   }
   Console.Write("done");
   //...
  }
}

//...
public async Task DoSomething(string p)
{
  if (p==null) throw new InvalidOperationException();
  else await SomeAsyncMethod();
}

Это будет заблокировано на каждом DoSomething до тех пор, пока оно не закончится и не выйдет из цикла, если DoSomething. Конечно, тогда вы на самом деле ничего не делаете асинхронно. Но из псевдокода я не могу сказать, что вы хотели сделать асинхронно.

Основной вывод: использование void для асинхронного метода означает, что вы теряете возможность получить исключение, если вы не await не используете этот метод асинхронизации. Как вызов синхронизации, он в основном просто планирует работу, и результат исчезает в эфир.

Ответ 2

Если вы предоставите своему консольному приложению контекст, совместимый с асинхронным (например, AsyncContext (docs, источник) из моей библиотеки AsyncEx), тогда вы можете перехватывать исключения, которые распространяются из этого контекста, даже из методов async void:

public static void Main()
{
  try
  {
    AsyncContext.Run(() => myobj.DoSomething(null));
  }
  catch (Exception ex)
  {
    Console.Error.WriteLine(ex.Message);
  }
  Console.Write("done");
}

public async void DoSomething(string p)
{
  if (p==null) throw new InvalidOperationException();
  else await SomeAsyncMethod();
}