async / wait - коли повернути Завдання проти пустоти?


502

За якими сценаріями ви хотіли б користуватися

public async Task AsyncMethod(int num)

замість

public async void AsyncMethod(int num)

Єдиний сценарій, про який я можу придумати, - це якщо вам потрібна задача, щоб мати можливість відстежувати її прогрес.

Крім того, в наступному методі чи не потрібні асинхронізація та очікування ключових слів?

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

20
Зверніть увагу , що методи повинні асинхронні завжди бути суфіксом з ім'ям Async.Example Foo()став би FooAsync().
Фред

30
@Fred Переважно, але не завжди. Це лише умова, і прийняті винятки з цієї конвенції складаються з класів на основі подій або договорів інтерфейсу, див. MSDN . Наприклад, не слід перейменовувати поширені обробники подій, наприклад, Button1_Click.
Бен

14
Просто замітка, яку ви не повинні використовувати Thread.Sleepзі своїми завданнями, які ви повинні await Task.Delay(num)замість цього
Боб Вале

45
@fred Я не згоден з цим, IMO, додаючи асинхронний суфікс, слід використовувати лише тоді, коли ви надаєте інтерфейс із параметрами синхронізації та асинхронізації. Smurf називає речі асинхронізацією, коли є лише один намір, безглуздо. Справа в суті Task.Delayне так, Task.AsyncDelayяк усі методи завдання - Async
Не любив

11
У мене вранці була цікава проблема з методом контролера webapi 2, це було оголошено як async voidзамість цього async Task. Метод вийшов з ладу через те, що він використовував контекстний об'єкт Entity Framework, оголошений членом контролера, до того, як метод закінчився для виконання. Рамка розмістила контролер до того, як його метод закінчився виконувати. Я змінив метод на асинхронізацію Завдання, і він спрацював.
Коста

Відповіді:


417

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


10
Я мав на увазі fзамість gсвого коментаря. Виняток з fпередається до SynchronizationContext. gпідніме UnobservedTaskException, але UTEбільше не збоїть процес, якщо його не обробляти. Є деякі ситуації, коли прийнятні такі "асинхронні винятки", які ігноруються.
Стівен Клірі

3
Якщо у вас є WhenAnyдекілька Tasks, що призводить до виключень. Вам часто доводиться поводитися лише з першим, а ви часто хочете ігнорувати інші.
Стівен Клірі

1
@StephenCleary Спасибі, я думаю, це гарний приклад, хоча це залежить від причини, яку ви телефонуєте, WhenAnyв першу чергу, чи добре ігнорувати інші винятки: основний випадок використання, який я маю для нього, все ще закінчується в очікуванні решти завдань, коли будь-які завершиться. , з винятком або без нього.

10
Я трохи розгублений, чому ви рекомендуєте повернути завдання замість недійсного. Як ви сказали, f () буде кидати і виняток, але g () не буде. Чи не найкраще бути поінформованим про ці винятки фонових ниток?
user981225

2
@ user981225 Дійсно, але це стає відповідальністю абонента g: кожен метод, який викликає g, повинен бути асинхронізованим і використовувати також. Це настанова, а не жорстке правило, ви можете вирішити, що у вашій конкретній програмі легше повернути g void.

40

Я натрапив на цю дуже корисну статтю про 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, але відновити процес з цієї події немає.

Під час написання обробників подій інтерфейсу користувача асинхронізовані методи недійсності якимось чином безболісні, оскільки винятки трактуються так само, як і у методах, що не містять асинхронізацію; їх кидають на диспетчера. Існує можливість відновитися після таких винятків, з більш ніж правильним для більшості випадків. Поза обробниками подій інтерфейсу користувача, асинхронізовані методи недійсності є якось небезпечними для використання, і їх не так просто знайти.


Це правильно? Я вважав, що винятки з методів асинхронізації фіксуються в Task. А також afaik завжди є типовий SynchronizationContext
NM

30

Я отримав чітке уявлення з цих тверджень.

  1. Методи анулювання ануляції мають різну семантику обробки помилок. Коли виняток викидається із задачі асинхронізації чи методу асинхронної задачі, цей виняток фіксується та розміщується на об’єкті завдання. З методами анулювання анулювання немає об’єкта Task, тому будь-які винятки, викинуті з методу анулювання анулювання, будуть підняті безпосередньо на SynchronizationContext (SynchronizationContext представляє місце ", де" може бути виконаний код.), Яке було активним, коли метод асинхронізації недійсний розпочато

Винятки з методу анулювання анулювання не можуть бути спіймані з ловом

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

Ці винятки можна спостерігати за допомогою AppDomain.UnhandledException або аналогічної події загального характеру для додатків GUI / ASP.NET, але використання цих подій для регулярної обробки винятків є рецептом незмінності (вона руйнує додаток).

  1. Методи асинхроністичних пустот мають різну семантику складання. Методи асинхрування, що повертають Завдання або Завдання, можуть бути легко складені за допомогою await, Task.WhenAny, Task.WhenAll тощо. Методи асинхронізації, що повертають недійсність, не забезпечують простий спосіб сповіщення про викликовий код, який вони виконали. Запустити декілька методів асинхронізації неважко, але визначити, коли вони закінчили. Недійсні методи асинхронізації повідомлять про їх синхронізаціюContext, коли вони починаються та закінчуються, але користувацький SynchronizationContext є складним рішенням для звичайного коду програми.

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

Більш детально ознайомтесь за цим посиланням https://msdn.microsoft.com/en-us/magazine/jj991977.aspx


21

Проблема з викликом асинхронної недійсності полягає в тому, що ви навіть не отримаєте завдання назад, ви не можете знати, коли завдання функції виконано (див. Https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/ ? p = 96655 )

Ось три способи викликати функцію асинхронізації:

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

У всіх випадках функція перетворюється на ланцюжок завдань. Різниця полягає в тому, що функція повертає.

У першому випадку функція повертає завдання, яке з часом виробляє t.

У другому випадку функція повертає завдання, у якого немає продукту, але ви все ще можете чекати, щоб дізнатися, коли воно закінчилося.

Третій випадок - бридкий. Третій випадок - це як другий випадок, за винятком того, що ви навіть не отримаєте завдання. Ви не можете знати, коли завдання функції виконано.

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


6

Я думаю, ви також можете використовувати 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());
        }
    }
}

1
Проблема з викликом асинхронної недійсності полягає в тому, що ви навіть не отримаєте завдання назад, у вас немає можливості дізнатися, коли завдання функції виконано
user8128167

Це має сенс для (рідкісних) фонових операцій, коли ви не піклуєтесь про результат і не хочете, щоб виняток впливав на інші операції. Типовим випадком використання буде надсилання події журналу на сервер журналу. Ви хочете, щоб це відбувалося у фоновому режимі, і ви не хочете, щоб ваш обробник послуг / запитів вийшов з ладу, якщо сервер журналу не працює. Звичайно, ви маєте наздогнати всі винятки, інакше ваш процес буде припинено. Або є кращий спосіб досягти цього?
Флоріан Зима

Винятки з методу анулювання анулювання не можуть бути спіймані з ловом. msdn.microsoft.com/en-us/magazine/jj991977.aspx
Si Zi

3
@SiZi улов є ВНУТРИ методом асинхронізації, як показано в прикладі, і він потрапляє.
bboyle1234

Як щодо того, коли ваші вимоги змінюються, щоб не вимагати ніякої роботи, якщо ви не можете це ввійти - тоді вам доведеться змінити підписи у всьому API, а не просто змінити логіку, яка наразі має // Ігнорувати виняток
Milney

0

Моя відповідь проста, не можна чекати методу недійсності

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

Отже, якщо метод є асинхронним, краще бути очікуваним, оскільки ви можете втратити перевагу асинхронізації.


-2

Згідно з документацією Microsoft , НІКОЛИ не слід використовуватиasync void

Не робіть цього: використовується наступний приклад, async voidякий робить запит HTTP завершеним після досягнення першого очікування:

  • Що ЗАВЖДИ є поганою практикою в додатках ASP.NET Core.

  • Отримує доступ до HttpResponse після завершення запиту HTTP.

  • Збиває процес.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.