async / await - когда вернуть задачу vs void?

при каких сценариях хотелось бы использовать

public async Task AsyncMethod(int num)

вместо

public async void AsyncMethod(int num)

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

кроме того, в следующем методе не нужны ключевые слова async и await?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

6 ответов


1) Обычно вы хотели бы вернуть Task. Основным исключением должно быть, когда вы нужно иметь void тип возврата (для событий). Если нет причин запрещать наличие вызывающего await ваша задача, почему запретить?

2) async методы, которые возвращают void специальные в другом аспекте: они представляют асинхронные операции верхнего уровня, и имеют дополнительные правила, которые вступают в игру, когда ваша задача возвращает исключение. Этот самый простой способ-показать разницу на примере:

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

fисключение всегда "наблюдается". Исключение, которое оставляет асинхронный метод верхнего уровня, просто обрабатывается как любое другое необработанное исключение. gисключение никогда не наблюдается. Когда сборщик мусора приходит для очистки задачи, он видит, что задача привела к исключению, и никто не обработал исключение. Когда это происходит,TaskScheduler.UnobservedTaskException обработчик работает. Ты не должен этого допустить. Чтобы использовать ваш пример,

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

Да, использовать async и await здесь они удостоверяются, что ваш метод все еще работает правильно, если возникает исключение.

дополнительная информация: http://msdn.microsoft.com/en-us/magazine/jj991977.aspx


я наткнулся на эту очень полезную статью о async и void автор: Жером Лабан: http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx

суть в том, что async+void может привести к сбою системы и обычно должен использоваться только на обработчиках событий стороны пользовательского интерфейса.

причиной этого является контекст синхронизации, используемого AsyncVoidMethodBuilder, не будучи ни в этом образец. Когда нет контекст синхронизации среды, любое исключение, не обработанное тело метода async void перестраивается в пуле потоков. В то время как кажется, нет другого логического места, где такого рода необработанный исключение может быть брошено, неудачный эффект заключается в том, что процесс завершается, потому что необработанные исключения в ThreadPool эффективно завершите процесс с .NET 2.0. Вы можете перехватить все необработанные исключения с использованием домен приложений.Событие UnhandledException, но нет никакого способа восстановить процесс из этого события.

при написании обработчиков событий UI методы async void каким-то образом безболезненно, потому что исключения обрабатываются так же, как в не-асинхронные методы; они бросаются на диспетчера. Есть возможность восстановления от таких исключений, с более чем правильно для большинства случаев. Однако вне обработчиков событий пользовательского интерфейса async void методы как-то опасны для использования и может быть не так легко найти.


Я получил четкое представление из этих утверждений.

  1. асинхронные методы void имеют различную семантику обработки ошибок. Когда исключение выбрасывается из асинхронной задачи или метода асинхронной задачи, это исключение захватывается и помещается в объект задачи. С методами async void нет объекта задачи, поэтому любые исключения, выброшенные из метода async void, будут вызываться непосредственно в SynchronizationContext (SynchronizationContext представляет местоположение "где" код может быть выполнен. ) который был активен при запуске метода async void

исключения из метода async Void не могут быть пойманы с помощью Catch

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

эти исключения можно наблюдать с помощью AppDomain.UnhandledException или аналогичное событие catch-all для GUI / ASP.NET приложений, но использование этих событий для регулярной обработки исключений является рецептом недостижимости (он аварийно завершает работу приложения).

  1. асинхронные методы void имеют разные композиции семантика. Асинхронные методы, возвращающие задачу или задачу, могут быть легко составлены с помощью await, Task.Метод Whenany, Задач.Когда все и так далее. Асинхронные методы, возвращающие void, не обеспечивают простой способ уведомить вызывающий код, который они завершили. Легко запустить несколько асинхронных методов void, но нелегко определить, когда они закончились. Async void методы будут уведомлять их SynchronizationContext при запуске и завершении, но пользовательский SynchronizationContext является сложным решением для обычного приложения код.

  2. метод async Void полезен при использовании синхронного обработчика событий, поскольку они вызывают свои исключения непосредственно в SynchronizationContext, который похож на поведение синхронных обработчиков событий

для более подробной информации, проверьте эту ссылку https://msdn.microsoft.com/en-us/magazine/jj991977.aspx


Я думаю, вы можете использовать async void для запуска фоновых операций, а также, если вы будете осторожны, чтобы поймать исключения. Мысли?

class Program {

    static bool isFinished = false;

    static void Main(string[] args) {

        // Kick off the background operation and don't care about when it completes
        BackgroundWork();

        Console.WriteLine("Press enter when you're ready to stop the background operation.");
        Console.ReadLine();
        isFinished = true;
    }

    // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
    static async void BackgroundWork() {
        // It's important to catch exceptions so we don't crash the appliation.
        try {
            // This operation will end after ten interations or when the app closes. Whichever happens first.
            for (var count = 1; count <= 10 && !isFinished; count++) {
                await Task.Delay(1000);
                Console.WriteLine($"{count} seconds of work elapsed.");
            }
            Console.WriteLine("Background operation came to an end.");
        } catch (Exception x) {
            Console.WriteLine("Caught exception:");
            Console.WriteLine(x.ToString());
        }
    }
}

проблема с вызовом async void заключается в том, что вы даже не возвращаете задачу, у вас нет способа узнать, когда задача функции завершена (см. https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/?p=96655)

вот три способа вызова асинхронной функции:

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

во всех случаях функция преобразуется в цепочку задач. Разница в том что функция возвращает.

в первом case, функция возвращает задачу, которая в конечном итоге создает t.

во втором случае функция возвращает задачу, у которой нет продукта, но вы можете все еще ждите на нем, чтобы узнать, когда он будет завершен.

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

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


мой ответ прост вы пушки ждать void метод Ошибка CS4008 не может ждать 'void' TestAsync e:\test\TestAsync\TestAsync\Program - ... cs

поэтому, если метод асинхронный, лучше быть ожидаемым, потому что вы можете потерять асинхронное преимущество.