Як безпечно викликати метод асинхронізації в C # без очікування


322

У мене є asyncметод, який не повертає даних:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

Я називаю це іншим методом, який повертає деякі дані:

public string GetStringData()
{
    MyAsyncMethod(); // this generates a warning and swallows exceptions
    return "hello world";
}

Виклик, MyAsyncMethod()не чекаючи, викликає попередження " Оскільки цього дзвінка не очікується, поточний метод продовжує працювати до завершення виклику " у візуальній студії. На сторінці цього попередження зазначено:

Вам слід розглянути можливість придушення попередження лише в тому випадку, якщо ви впевнені, що не хочете чекати завершення асинхронного виклику і що викликаний метод не створює жодних винятків .

Я впевнений, що не хочу чекати завершення дзвінка; Мені не потрібно і не маю часу на це. Але виклик може спричинити винятки.

Я кілька разів натрапив на цю проблему і впевнений, що це загальна проблема, яка повинна мати спільне рішення.

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

Оновлення:

Для людей, які підказують, що я просто чекаю результату, це код, який відповідає на веб-запит нашої веб-служби (ASP.NET Web API). Очікування в контексті користувальницького інтерфейсу забезпечує потік користувальницького інтерфейсу безкоштовним, але очікування у виклику веб-запиту зачекає завершення завдання перед тим, як відповісти на запит, тим самим збільшуючи час відповіді без причини.


Чому б просто не створити метод завершення і просто ігнорувати його там? Тому що якщо він працює на фоновому потоці. Тоді це все одно не зупинить вашу програму.
Ях’я

1
Якщо ви не хочете чекати результату, єдиний варіант - проігнорувати / придушити попередження. Якщо ви дійсно хочете чекати результату / крім потімMyAsyncMethod().Wait()
Peter Ritchie

1
Про вашу редакцію: це не має для мене сенсу. Скажіть, що відповідь надсилається клієнту через 1 сек після запиту, і через 2 секунди ваш метод асинхронізації видає виняток. Що б ви зробили з цим винятком? Ви не можете надіслати його клієнту, якщо ваша відповідь вже надіслана. Що б ти ще робив з цим?

2
@ Ромоку Досить справедливо. Якщо припустити, хтось дивиться на колоду. :)

2
Варіант сценарію веб-API ASP.NET - це власний веб-API в довготривалому процесі (наприклад, служба Windows), де запит створює тривале фонове завдання зробити щось дороге, але все-таки хоче швидко отримати відповідь за допомогою HTTP 202 (Прийнято).
Девід Рубін

Відповіді:


186

Якщо ви хочете отримати виняток "асинхронно", ви можете зробити:

  MyAsyncMethod().
    ContinueWith(t => Console.WriteLine(t.Exception),
        TaskContinuationOptions.OnlyOnFaulted);

Це дозволить вам опрацьовувати виняток на потоці, відмінній від "головної" теми. Це означає, що вам не доведеться "чекати" дзвінка MyAsyncMethod()з потоку, який дзвонить MyAsyncMethod; але, все ще дозволяє зробити щось із винятком - але лише у випадку, якщо виняток має місце.

Оновлення:

технічно ви можете зробити щось подібне з await:

try
{
    await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
    Trace.WriteLine(ex);
}

... що було б корисно, якщо вам потрібно спеціально використовувати try/ catch(або using), але я вважаю, що ContinueWithце трохи більш чітко, тому що ви повинні знати, що ConfigureAwait(false)означає.


7
Я перетворив це на метод розширення на Task: public static class AsyncUtility {public static void PerformAsyncTaskWithoutAwait (це завдання завдання, Action <Task> виключенняHandler) {var dummy = task.ContinueWith (t => виключенняHandler (t), TaskContinuationOptions.OnlyOnFaulted }} Використання: MyAsyncMethod (). PerformAsyncTaskWithoutAwait (t => log.ErrorFormat ("Виникла помилка під час виклику MyAsyncMethod: \ n {0}", t.Exception));
Марк Авениус

2
downvoter. Коментарі? Якщо у відповіді щось не так, я хотів би знати та / або виправити.
Пітер Річі

2
Гей - я не заявив, але ... Чи можете ви пояснити своє оновлення? Цього дзвінка не слід було чекати, тож якщо ви зробите це так, то ви дійсно чекаєте дзвінка, просто не продовжуйте у захопленому контексті ...
Bartosz

1
"чекати" не є правильним описом у цьому контексті. Рядок після ConfiguratAwait(false)не виконується, поки завдання не буде виконано, але поточний потік не "чекає" (тобто блокує) для цього, наступний рядок викликається асинхронно до виклику очікування. Без ConfigureAwait(false)цього наступний рядок буде виконуватися у вихідному контексті веб-запиту. З цим ConfigurateAwait(false)він виконується в тому ж контексті, що і метод асинхронізації (завдання), звільняючи початковий контекст / потік для продовження ...
Пітер Річі

15
Версія ContinueWith не є такою ж, як версія спробу {await} catch {}. У першій версії все після ContinueWith () виконується негайно. Початкове завдання звільнене і забуте. У другій версії все після вилову {} буде виконано лише після завершення початкового завдання. Друга версія еквівалентна "очікуйте MyAsyncMethod (). ContinueWith (t => Console.WriteLine (t.Exception), TaskContinuationOptions.OnlyOnFaulted) .ConfigureAwait (fals);
Thanasis Ioannidis

67

Ви повинні спочатку розглянути питання про внесення GetStringDataв asyncметод і він awaitзадача повернувся з MyAsyncMethod.

Якщо ви абсолютно впевнені, що вам не потрібно обробляти винятки з MyAsyncMethod або знаєте, коли це завершується, ви можете зробити це:

public string GetStringData()
{
  var _ = MyAsyncMethod();
  return "hello world";
}

До речі, це не є "загальною проблемою". Дуже рідко хочеться виконати якийсь код і неважливо, чи завершує він, і не байдуже, чи завершує він успішно.

Оновлення:

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

Отже, це прекрасне рішення для чогось такого простого, як закидання події в журнал, де це не має особливого значення, якщо ви втратите кілька тут і там. Це не гарне рішення для будь-яких операцій, що мають важливе значення для бізнесу. У таких ситуаціях ви повинні прийняти більш складну архітектуру, що має стійкий спосіб збереження операцій (наприклад, черги Azure, MSMQ) та окремий фоновий процес (наприклад, роль робочого Azure, служба Win32) для їх обробки.


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

5
@GeorgePowell: Дуже небезпечно працювати з кодом у контексті ASP.NET без активного запиту. У мене є повідомлення в блозі, яке може допомогти вам , але, не знаючи більше вашої проблеми, я не можу сказати, рекомендував би я такий підхід чи ні.
Стівен Клірі

@StephenCleary У мене є аналогічна потреба. У моєму прикладі у мене є / потрібен двигун пакетної обробки для запуску в хмарі, я "пінг" кінцевої точки, щоб почати пакетну обробку, але хочу повернутися негайно. Оскільки він починає працювати, він може обробляти все звідти. Якщо є винятки, які викидаються, вони просто заносяться до моїх таблиць "BatchProcessLog / Error" ...
ganders

8
У C # 7 ви можете замінити var _ = MyAsyncMethod();на _ = MyAsyncMethod();. Це все ще уникає попередження CS4014, але це робить трохи більш явним, що ви не використовуєте змінну.
Брайан

48

Відповідь Пітера Річі - те, чого я хотів, і стаття Стівена Клірі про повернення на ASP.NET була дуже корисною.

Однак, як більш загальна проблема (не специфічна для контексту ASP.NET), наступна програма консолі демонструє використання та поведінку відповіді Петра за допомогою Task.ContinueWith(...)

static void Main(string[] args)
{
  try
  {
    // output "hello world" as method returns early
    Console.WriteLine(GetStringData());
  }
  catch
  {
    // Exception is NOT caught here
  }
  Console.ReadLine();
}

public static string GetStringData()
{
  MyAsyncMethod().ContinueWith(OnMyAsyncMethodFailed, TaskContinuationOptions.OnlyOnFaulted);
  return "hello world";
}

public static async Task MyAsyncMethod()
{
  await Task.Run(() => { throw new Exception("thrown on background thread"); });
}

public static void OnMyAsyncMethodFailed(Task task)
{
  Exception ex = task.Exception;
  // Deal with exceptions here however you want
}

GetStringData()повертається достроково, не чекаючи, MyAsyncMethod()і винятки, які викидаються MyAsyncMethod(), розглядаються в, OnMyAsyncMethodFailed(Task task)а не в try/ catchнавколоGetStringData()


9
Видаліть Console.ReadLine();і додайте трохи сну / затримки, MyAsyncMethodі ви ніколи не побачите винятку.
tymtam

17

Я закінчую це рішення:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

public string GetStringData()
{
    // Run async, no warning, exception are catched
    RunAsync(MyAsyncMethod()); 
    return "hello world";
}

private void RunAsync(Task task)
{
    task.ContinueWith(t =>
    {
        ILog log = ServiceLocator.Current.GetInstance<ILog>();
        log.Error("Unexpected Error", t.Exception);

    }, TaskContinuationOptions.OnlyOnFaulted);
}

8

Це називається вогнем і забудьте, і для цього є розширення .

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

Встановіть nuget пакет .

Використання:

MyAsyncMethod().Forget();

12
Це нічого не робить, це лише хитрість зняти попередження. Дивіться github.com/microsoft/vs-threading/blob/master/src/…
Paolo Fulgoni

2

Я думаю, виникає запитання, навіщо вам це робити? Причина asyncв C # 5.0 полягає в тому, що ви можете чекати результату. Цей метод насправді не є асинхронним, а просто викликається за раз, щоб не заважати занадто сильно поточній нитці.

Можливо, може бути краще запустити нитку і залишити її закінчувати самостійно.


2
asyncце трохи більше, ніж просто "очікування" результату. "очікувати" означає, що рядки, що слідують за "очікувати", виконуються асинхронно на тому ж потоці, що викликав "очікувати". Зрозуміло, це можна зробити без "очікування", але ви, в кінцевому рахунку, маєте купу делегатів і втратите послідовний вигляд коду (а також можливість використання usingта try/catch...
Пітер Річі,

1
@PeterRitchie Ви можете мати метод, який функціонально асинхронний, не використовує awaitключове слово і не використовує asyncключове слово, але немає сенсу використовувати asyncключове слово, не використовуючи також awaitу визначенні цього методу.
Сервіс

1
@PeterRitchie З твердження: " asyncце трохи більше, ніж просто" очікування "результату". asyncКлючові слова (ви на увазі це ключове слово, уклавши його в зворотних лапках) означає нічого більш , ніж в очікуванні результату. Це асинхронність, як загальні поняття CS, означає більше, ніж просто очікування результату.
Сервіс

1
@Servy asyncстворює державну машину, яка управляє будь-якими очікуваннями методу async. Якщо awaitв методі немає s, він все одно створює цю машину стану, але метод не є асинхронним. І якщо asyncметод повернеться void, чекати нічого не можна. Отже, це більше, ніж просто очікувати результату.
Пітер Річі

1
@PeterRitchie Поки метод повертає завдання, ви можете його чекати. Немає потреби в машині стану чи asyncключовому слові. Все, що вам потрібно буде зробити (і все, що дійсно відбувається в кінцевому підсумку зі стаціонарною машиною в цьому спеціальному випадку), - це те, що метод запускається синхронно і потім завершується виконаним завданням. Я припускаю, що ви технічно не просто виймаєте державну машину; ви виймаєте стан машини, а потім дзвоните Task.FromResult. Я припускав, що ви (а також письменники-компілятори) можете додати додаток самостійно.
Сервіс

0

У технологіях із циклами повідомлень (не впевнений, чи є ASP одним із них), ви можете заблокувати цикл та обробляти повідомлення, поки завдання не закінчиться, і використовувати ContinueWith для розблокування коду:

public void WaitForTask(Task task)
{
    DispatcherFrame frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false));
    Dispatcher.PushFrame(frame);
}

Цей підхід схожий на блокування ShowDialog і все ще підтримує інтерфейс користувача чутливим.


0

Я запізнююсь на вечірку тут, але є дивовижна бібліотека, яку я використовую, про яку я не бачив посилання в інших відповідях

https://github.com/brminnick/AsyncAwaitBestPractices

Якщо вам потрібно "Запустити і забути", ви викликаєте метод розширення для завдання.

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

У вашому прикладі ви б використовували його так:

   public string GetStringData()
    {
        MyAsyncMethod().SafeFireAndForget(onException: (exception) =>
                    {
                      //DO STUFF WITH THE EXCEPTION                    
                    }); 
        return "hello world";
    }

Він також дає очікувані AsyncCommands, що реалізують ICommand з коробки, що чудово підходить для мого рішення MVVM Xamarin


-1

Рішення полягає в запуску HttpClient в інше завдання виконання без контексту синхронізації:

var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);

-1

Якщо ти справді хочеш це зробити. Просто для адреси "Виклик асинхронного методу в C # без очікування", ви можете виконати метод асинхронізації всередині Task.Run. Такий підхід буде чекати до MyAsyncMethodкінця.

public string GetStringData()
{
    Task.Run(()=> MyAsyncMethod()).Result;
    return "hello world";
}

awaitасинхронно розгортає Resultзавдання, тоді як лише використання результату блокується до завершення завдання.


-1

Зазвичай метод асинхронізації повертає клас Завдання. Якщо ви використовуєте Wait()метод або Resultвластивість та викиди коду, виняток - тип винятку загортається, AggregateException- вам потрібно здійснити запит, Exception.InnerExceptionщоб знайти правильний виняток.

Але це також можна використовувати .GetAwaiter().GetResult()замість цього - він також буде чекати завдання асинхронізації, але не завершує виняток.

Ось короткий приклад:

public async Task MyMethodAsync()
{
}

public string GetStringData()
{
    MyMethodAsync().GetAwaiter().GetResult();
    return "test";
}

Можливо, ви також можете мати можливість повернути деякий параметр з функції асинхронізації - цього можна досягти, надавши додаткову Action<return type>функцію async, наприклад, наприклад:

public string GetStringData()
{
    return MyMethodWithReturnParameterAsync().GetAwaiter().GetResult();
}

public async Task<String> MyMethodWithReturnParameterAsync()
{
    return "test";
}

Зверніть увагу, що в методах асинхронізації зазвичай є ASyncіменні суфікси, щоб уникнути зіткнення між синхронізуючими функціями з тим самим іменем. (Наприклад FileStream.ReadAsync) - я оновив назви функцій, щоб дотримуватися цієї рекомендації.

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