предупреждение: этот вызов не ожидается, выполнение текущего метода продолжается


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не асинхронный, этот метод потребовал бы вложения обратного вызова в обратный вызов. С помощью async я могу написать свой метод очень естественным образом:

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 и позволить 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();
    }

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

Есть ли что-то неправильное в моем использовании async?


2
Кстати, при внедрении PromptForStringAsyncвы делаете больше работы, чем вам нужно; просто верните результат Task.Factory.StartNew. Это уже задача, значение которой - строка, введенная в консоли. Не нужно ждать возврата результата; это не добавляет никакой новой ценности.
Servy

Разве не имеет смысла GetNameAsyncпредоставить полное имя, которое было предоставлено пользователем (т.е. Task<Name>вместо того, чтобы просто возвращать Task?), DoStuffМожно было бы сохранить эту задачу, и либо awaitее после другого метода, либо даже передать задачу этому другому метод, чтобы он мог awaitили Waitгде-то внутри его реализации.
Servy

@Servy: если я просто верну задачу, я получаю сообщение об ошибке: «Поскольку это асинхронный метод, возвращаемое выражение должно иметь тип 'строка', а не 'Задача <строка>'».
Mud

1
Удалите asyncключевое слово.
Servy

13
ИМО, это был плохой выбор для предупреждения со стороны команды C #. Предупреждения должны быть о вещах, которые почти наверняка ошибочны. Есть много случаев, когда вы хотите «запустить и забыть» асинхронный метод, и в других случаях, когда вы действительно хотите его дождаться.
MgSam

Ответы:


103

Если вам действительно не нужен результат, вы можете просто изменить GetNameAsyncподпись, чтобы вернуть void:

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

Попробуйте увидеть ответ на связанный вопрос: в чем разница между возвратом void и возвратом задачи?

Обновить

Если вам нужен результат, вы можете изменить его GetNameAsyncна return, например 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
Это могло быть только в том случае, если он никогда не заботился о результате, а не просто не нуждался в результате в этот раз .
Servy

3
@Servy, правильно, спасибо за разъяснения. Но OP GetNameAsyncне возвращает никакого значения (кроме самого результата, конечно).
Николай Хиль

2
Верно, но, вернув задачу, он может узнать, когда она завершит выполнение всех асинхронных операций. Если он вернется void, он не знает, когда это будет сделано. Именно это я имел в виду, когда сказал «результат» в своем предыдущем комментарии.
Servy

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 .

Исключения, возникшие во время ожидаемого асинхронного вызова, будут потеряны. Чтобы избавиться от этого предупреждения, вы должны присвоить переменной, возвращаемое Task, значение асинхронного вызова. Это гарантирует, что у вас будет доступ ко всем созданным исключениям, которые будут указаны в возвращаемом значении.

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

В C # 7.0 добавлена ​​новая функция сброса переменных: Discards - Руководство по C # , которая также может помочь в этом отношении.

_ = SomeMethodAsync();

5
Это абсолютно круто. Я понятия не имел, что вы можете это сделать. Спасибо!
Максим Гершкович

1
Даже не требуется говорить var, просто напишите_ = SomeMethodAsync();
Рэй

41

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

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

Метод расширения:

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

Использование:

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

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


1
Мне это нравится, но я переименовал его в Unawait ();)
Ostati 02

29

async void ПЛОХО!

  1. В чем разница между возвратом void и возвратом задачи?
  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; });

Ваше расширение Task потрясающее. Не знаю, почему я не реализовал его раньше.
Mikael Dúi Bolinder

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 / await.
gregsdennis

Вы должны встать, сэр!
Ozkan

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. Похоже, что это позволяет избежать всех проблем, которые сравниваются в комментариях к другим ответам. Спасибо.
Grault

3
Вам просто нужно это: public static void Forget(this Task task) { }
Шитал Шах

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если у вас нет веской причины не делать этого. Здесь нет причин, по которым отказ от задачи был бы лучше, чем уже принятый ответ об использовании async voidметода.

Я также предпочитаю метод void. Это делает StackOverflow умнее Microsoft, но, конечно, это должно быть дано.
devlord

4
async voidсоздает серьезные проблемы с обработкой ошибок и приводит к непроверяемому коду (см. мою статью MSDN ). Было бы гораздо лучше использовать переменную - если вы абсолютно уверены, что хотите, чтобы исключения проглатывались молча. Скорее всего, Taskоператор захочет начать две с, а затем выполнить 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: просто отправьте результат первого асинхронного вызова в качестве параметра для второго вызова. Таким образом, код во втором методе запускается сразу же и может ждать результата первого вызова, когда ему это нравится.
Guffa

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

@ Mud: Да. Но вы можете вызвать второй асинхронный метод сразу после первого, у него нет причин ждать синхронного вызова Use.
Guffa

Я действительно не понимаю, что вы говорите. MethodWithCallback является асинхронным. Если я перезвоню им, не дожидаясь первого звонка, второй звонок может закончиться раньше первого. Однако мне нужен результат первого вызова в обработчике второго. Так что мне нужно дождаться первого звонка.
Mud

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.