попередження цього дзвінка не чекає, виконання поточного методу продовжується


135

Щойно отримав VS2012 і намагався впоратися async.

Скажімо, у мене є метод, який отримує якесь значення з джерела блокування. Я не хочу, щоб викликав метод блокувати. Я міг би написати метод зворотного виклику, який викликається, коли значення надходить, але оскільки я використовую C # 5, я вирішу зробити метод асинхронізован, щоб абонентам не довелося мати справу з зворотними зворотами:

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

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

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

Все йде нормально. Проблема полягає в тому, коли я телефоную GetNameAsync:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

Вся справа в GetNameAsyncтому, що це асинхронно. Я не хочу, щоб це блокувалося, тому що я хочу повернутися до MainWorkOfApplicationIDontWantBlocked ASAP і дозволити GetNameAsync робити свою справу у фоновому режимі. Однак, називаючи це таким чином, я попереджаю компілятор у GetNameAsyncрядку:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Я прекрасно знаю, що "виконання поточного методу триває до завершення виклику". Це суть асинхронного коду, правда?

Я віддаю перевагу своєму коду компілювати без попереджень, але тут нічого не можна «виправити», оскільки код робить саме те, що я маю намір це робити. Я можу позбутися попередження, зберігаючи повернене значення GetNameAsync:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

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

Я також можу позбутися попередження, загорнувши GetNameAsync у метод, який не є асинхронним:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

Але це ще більш зайвий код. Тож я мушу писати код, який мені не потрібен або терплять зайве попередження.

Чи є щось про моє використання асинхрони, що тут не так?


2
До речі, при впровадженні PromptForStringAsyncви робите більше роботи, ніж потрібно; просто повернути результат Task.Factory.StartNew. Це вже завдання, яким є значення рядка, введеного в консоль. Не потрібно чекати повернення результату; це не додає нової цінності.
Сервіс

Чи не було б більше сенсу вказувати GetNameAsyncповне ім’я, яке було надано користувачем (тобто Task<Name>, а не просто повертати Task??), DoStuffМоже потім зберігати це завдання, або awaitвоно після іншого методу, або навіть передавати завдання тому іншому метод, щоб він міг awaitчи Waitце десь усередині його реалізації
Сервіс

@Servy: Якщо я просто повертаю завдання, я отримую помилку "Оскільки це метод асинхронізації, вираз повернення повинен мати тип" string ", а не" Task <string> "".
Грязь

1
Видаліть asyncключове слово.
Сервіс

13
IMO, це був поганий вибір для попередження з боку команди C #. Попередження повинні стосуватися речей, які майже напевно неправильні. Існує маса випадків, коли ви хочете "запустити і забути" метод асинхронізації та інші випадки, коли ви насправді хочете його чекати.
MgSam

Відповіді:


103

Якщо результат вам дійсно не потрібен, ви можете просто змінити GetNameAsyncпідпис на повернення void:

public static async void GetNameAsync()
{
    ...
}

Розглянемо відповідь на відповідне запитання: Яка різниця між поверненням порожнечі та поверненням завдання?

Оновлення

Якщо вам потрібен результат, ви можете змінити GetNameAsyncна повернення, скажімо Task<string>:

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

І використовуйте його наступним чином:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}

15
Це було б тільки в тому випадку , якщо він ніколи не піклується про результат, на відміну від просто не потребуючи результаті це один рази .
Сервіс

3
@Servy, правильно, дякую за роз’яснення. Але ОП GetNameAsyncне повертає жодного значення (звичайно, крім самого результату).
Микола Хиль

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

29
Як правило, ви ніколи не повинні мати async voidметод, крім обробників подій.
Даніель Манн

2
Ще одна річ, яку слід зауважити, - це те, що поведінка відрізняється, коли мова йде про неспостережувані винятки, коли ви переходите на async voidбудь-який виняток, який ви не спіймаєте, приведе до краху ваш процес, але в .net 4.5 він буде продовжувати працювати.
Caleb Vear

62

Я досить запізнююся на цю дискусію, але є також можливість використовувати #pragmaдирективу перед процесором. У мене є якийсь код асинхронізації тут і там, якого явно не хочу чекати в деяких умовах, і мені не подобаються попередження та невикористані змінні, як і решта з вас:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

"4014"Приходить з цієї сторінки MSDN: Попередження компілятора (рівень 1) CS4014 .

Дивіться також попередження / відповідь від @ ryan-horath тут https://stackoverflow.com/a/12145047/928483 .

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

Оновлення для C # 7.0

C # 7.0 додає нову функцію, відкиньте змінні: Відкидання - Посібник по C # , який також може допомогти у цьому плані.

_ = SomeMethodAsync();

5
Це абсолютно дивовижно. Я не уявляв, що ти можеш це зробити. Дякую!
Максим Гершкович

1
Про це навіть не потрібно говорити var, просто пишіть_ = SomeMethodAsync();
Рей

41

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

Моє рішення - створити метод розширення Завдання під назвою DoNotAwait (), який нічого не робить. Це не тільки придушить усі попередження, ReSharper чи іншим чином, але зробить код більш зрозумілим і вкаже майбутнім обслуговувачам вашого коду, що ви дійсно планували, щоб дзвінок не чекали.

Спосіб розширення:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

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

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

Відредаговано, щоб додати: це схоже на рішення Джонатана Аллена, де метод розширення запустив би завдання, якщо він ще не був запущений, але я вважаю за краще мати одноцільові функції, щоб наміри абонента були повністю зрозумілими.


1
Мені це подобається, але я перейменував його на Unawait ();)
Остаті

29

async void ПОГАНО!

  1. Яка різниця між поверненням порожнечі та поверненням завдання?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Я пропоную вам явно запустити Taskанонімний метод ...

напр

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

Або якщо ви хочете, щоб його блокували, ви можете зачекати на анонімному методі

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

Однак якщо ваш GetNameAsyncметод повинен взаємодіяти з інтерфейсом користувача або навіть чим-небудь, що пов'язаний з інтерфейсом користувача (WINRT / MVVM, я дивлюся на вас), то він стає трохи цікавішим =)

Вам потрібно буде передати посилання на диспетчер інтерфейсу, як це ...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

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

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

Ваше розширення Завдання приголомшливо. Не знаю, чому я її раніше не реалізував.
Мікаель Дуї Болінджер

8
Перший підхід, який ви згадуєте, викликає інше попередження: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. це також спричиняє створення нової нитки, тоді як новий потік не обов'язково буде створений лише в режимі async / очікувати.
gregsdennis

Ви повинні бути додому!
Озкан

16

Це те, що я зараз роблю:

SomeAyncFunction().RunConcurrently();

Де RunConcurrentlyвизначено як ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/


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

3
Вам просто потрібно це: public static void Forget(this Task task) { }
Shital Shah

1
що ти там маєш на увазі @ShitalShah
Jay Wick

@ShitalShah, який працюватиме лише із завданнями автозапуску, такими, як створені async Task. Деякі завдання потрібно запустити вручну.
Джонатан Аллен

7

Згідно зі статтею Microsoft щодо цього попередження, вирішити його можна, просто призначивши повернене завдання змінній. Нижче наведено переклад коду, наведений у прикладі Microsoft:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

Зауважте, що це призведе до повідомлення "Локальна змінна ніколи не використовується" в ReSharper.


Так, це пригнічує попередження, але ні, це насправді нічого не вирішує. Task-повернення функції повинно бути await-ed, якщо у вас є дуже вагомі причини цього не робити. Тут немає жодної причини, чому відмова від завдання була б кращою за прийняту відповідь щодо використання async voidметоду.

Я також віддаю перевагу методу недійсності. Це робить StackOverflow розумнішим за Microsoft, але, звичайно, це має бути дано.
devlord

4
async voidстворює серйозні проблеми навколо поводження з помилками і призводить до неперевіреного коду (див. мою статтю MSDN ). Було б набагато краще використовувати змінну - якщо ви абсолютно впевнені, що хочете, щоб винятки мовчки проковтувались. Швидше за все, оператор хотів би почати два Tasks, а потім зробити an await Task.WhenAll.
Стівен Клірі

@StephenCleary Налаштовується те, чи не враховуються винятки із незабезпечених завдань. Якщо є шанс, що ваш код буде використаний в чужому проекті, не допускайте цього. Для async void DoNotWait(Task t) { await t; }уникнення недоліків async voidописаних вами методів можна використовувати простий помічник . (І я не думаю Task.WhenAll, що цього хоче ОП, але це дуже добре могло б бути.)

@hvd: Ваш хелперний метод зробив би його перевіреним, але він все ще матиме дуже погану підтримку обробки обставин.
Стівен Клірі

3

Тут просте рішення.

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

З повагою


1

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

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


Я зробити хочу використовувати дані витягуються з блокування джерела, але я не хочу , щоб заблокувати абонент в той час як я чекаю цього. Без асинхронізації це можна зробити, передавши зворотний дзвінок. У своєму прикладі я маю два асинхронні методи, які потрібно викликати послідовно, а для другого виклику потрібно використовувати значення, повернене першим. Це означало б вкладення обробників зворотних викликів, що стає негарним як пекло. У документації я читав передбачає , що це саме те , що asyncбуло розроблено , щоб очистити ( наприклад )
Mud

@Mud: Просто надішліть результат від першого виклику асинхронізації як параметр другого виклику. Таким чином, код у другому методі починається відразу, і він може чекати результату від першого дзвінка, коли відчує, що це подобається.
Guffa

Я можу це зробити, але без асинхронізації, що означає вкладені зворотні виклики, як це: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })Навіть у цьому тривіальному прикладі це сука для розбору. З асинхронному, еквівалентний код генерується для мене , коли я пишу result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); що набагато легше читати (хоча ні один не надто читається розбили разом в цьому коментарі!)
Mud

@Mud: Так. Але ви можете зателефонувати другий метод асинхронізації відразу після першого, немає жодної причини чекати синхронного виклику на Use.
Гуффа

Я дійсно не розумію, що ти кажеш. МетодWithCallback є асинхронним. Якщо я передзвоню їм назад, не чекаючи першого дзвінка, другий дзвінок може закінчитися до першого. Однак мені потрібен результат першого дзвінка в обробнику для другого. Тому мені доведеться чекати на перший дзвінок.
Грязь

0

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

Якщо ні, то, можливо, ви захочете поглянути на це питання: " Пожежа та забудьте" ,


0

Якщо ви не хочете змінювати підпис методу на повернення void(оскільки повернення voidзавжди має бути недійсним редагуванням), ви можете використовувати функцію C # 7.0+ Discard, подібну цій, що трохи краще, ніж присвоєння змінної (і має видалити більшість інших попередження інструментів перевірки джерел):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.