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

Просто получил VS2012 и попытался получить дескриптор на async.

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

// contrived example (edited in response to Servy 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 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 в методе, который не async:

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

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

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

Ответ 1

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

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

Рассмотрите возможность ответа на соответствующий вопрос: Какая разница между возвратом void и возвратом задачи?

Обновление

Если вам нужен результат, вы можете изменить 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;
}

Ответ 2

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

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

"4014" исходит от этой страницы MSDN: Предупреждение компилятора (уровень 1) CS4014.

Смотрите также предупреждение/ответ @ryan-horath здесь fooobar.com/questions/70783/....

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

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

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

var _ = SomeMethodAsync();

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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; });

Ответ 5

Вот что я сейчас делаю:

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/

Ответ 6

Согласно статье 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.

Ответ 7

Здесь простое решение.

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

Привет

Ответ 8

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

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

Ответ 9

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

Если нет, вы можете взглянуть на этот вопрос: Пожар и Забытый подход,

Ответ 10

Если вы не хотите изменять сигнатуру метода для возврата void (поскольку всегда следует избегать возврата void), вы можете использовать С# 7. 0+ Отменить функцию, например, немного лучше, чем присвоение переменной (и следует удалить предупреждения большинства других инструментов проверки исходного кода):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}