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

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

public async Task AsyncMethod(int num)

вместо

public async void AsyncMethod(int num)

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

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

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

Ответ 1

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

Ответ 2

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

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

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

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

Ответ 3

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

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

Исключения из метода асинхронной недействительности Cant Be Caught with 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. Асинхронные методы имеют разную семантику. Асинхронные методы, возвращающие Задачу или Задачу, могут быть легко скомпилированы с использованием await, Task.WhenAny, Task.WhenAll и т.д. Асинхронные методы, возвращающие void, не предоставляют простой способ уведомить вызывающий код, который они завершили. Его легко начать несколько асинхронных методов, но нелегко определить, когда они закончили. Методы Async void будут уведомлять свой SynchronizationContext, когда они начинаются и заканчиваются, но настраиваемый SynchronizationContext является сложным решением для обычного кода приложения.

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

Подробнее об этой ссылке https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Ответ 4

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

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

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

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

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

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

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

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

Ответ 5

Я думаю, вы можете использовать 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 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());
        }
    }
}

Ответ 6

Мой ответ прост Вы не можете ждать пустого метода

Error   CS4008  Cannot await 'void' TestAsync   e:\test\TestAsync\TestAsyncProgram.cs

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