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

156

Это не дубликат «Как безопасно вызвать асинхронный метод в C # без ожидания» .

Как мне приятно подавить следующее предупреждение?

предупреждение CS4014: поскольку этот вызов не ожидается, выполнение текущего метода продолжается до завершения вызова. Попробуйте применить оператор 'await' к результату вызова.

Простой пример:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Что я пробовал и не понравилось

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

Обновленный , так как исходный принятый ответ был отредактирован, я изменил принятый ответ на тот, который использует C # 7.0 , поскольку я не думаю, что ContinueWithздесь уместно. Всякий раз, когда мне нужно регистрировать исключения для операций запуска и забывания, я использую более сложный подход, предложенный Стивеном Клири здесь .

noseratio
источник
1
Так ты думаешь #pragmaне приятно?
Фредерик Хамиди
10
@ Фредерик Хамиди, я делаю.
нос
2
@Noseratio: Ах, верно. Извините, я думал, что это было другое предупреждение. Игнорируй меня!
Джон Скит
3
@Terribad: Я не совсем уверен - кажется, что предупреждение довольно разумно для большинства случаев. В частности, вы должны подумать о том, что вы хотите, чтобы происходили с любыми сбоями - обычно даже для «запуска и забывания» вы должны решить, как регистрировать сбои и т. Д.
Джон Скит,
4
@Terribad, прежде чем использовать его тем или иным способом, вы должны иметь четкое представление о том, как распространяются исключения для асинхронных методов (отметьте это ). Затем ответ @ Knaģis предоставляет элегантный способ не потерять исключения для «забей и забудь» с помощью async voidвспомогательного метода.
нос

Ответы:

160

С C # 7 теперь вы можете использовать сброс :

_ = WorkAsync();
Энтони Визер
источник
7
Это удобная маленькая языковая функция, которую я просто не могу вспомнить. Это как будто _ = ...в моем мозгу.
Марк Л.
3
Я обнаружил, что SupressMessage удалил мое предупреждение из «Списка ошибок» Visual Studio, но не «Вывод», и #pragma warning disable CSxxxxвыглядит более уродливо, чем сброс;)
Дэвид Сэвидж
122

Вы можете создать метод расширения, который предотвратит предупреждение. Метод расширения может быть пустым, или вы можете добавить обработку исключений .ContinueWith()там.

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

Однако ASP.NET подсчитывает количество запущенных задач, поэтому он не будет работать с простым Forget()расширением, как указано выше, и вместо этого может произойти сбой за исключением:

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

В .NET 4.5.2 это можно решить с помощью HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}
Knaģis
источник
8
Я нашел TplExtensions.Forget. Там намного больше добра Microsoft.VisualStudio.Threading. Мне бы хотелось, чтобы он был доступен для использования вне Visual Studio SDK.
нос
1
@Noseratio и Knagis, мне нравится этот подход, и я планирую его использовать. Я опубликовал следующий вопрос: stackoverflow.com/questions/22864367/fire-and-forget-approach
Мэтт Смит
3
@stricq Какую цель послужит добавлением ConfigureAwait (false) для Forget ()? Насколько я понимаю, ConfigureAwait влияет только на синхронизацию потоков в той точке, где ожидание используется в Задаче, но цель Forget () состоит в том, чтобы отбросить Задачу, чтобы Задача никогда не ожидалась, поэтому ConfigureAwait здесь не имеет смысла.
dthorpe
3
Если порождающий поток завершается до того, как задание запуска и забывания завершено, без ConfigureAwait (false) он все равно будет пытаться маршалировать себя обратно в порождающий поток, этот поток исчезнет, ​​так что он заблокируется. Установка ConfigureAwait (false) говорит системе не выполнять маршалинг обратно в вызывающий поток.
сентября
2
В этом ответе есть правка для управления конкретным делом, а также десяток комментариев. Просто вещи часто бывают правильными, иди на выбросы! И я цитирую ответ @ fjch1997: глупо создавать метод, для выполнения которого требуется еще несколько тиков, просто с целью подавления предупреждения.
Teejay
39

Вы можете украсить метод следующим атрибутом:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

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

Важной частью этого кода является второй параметр. Часть «CS4014:» - это то, что подавляет предупреждение. Вы можете написать все, что вы хотите на отдыхе.

Фабиу
источник
У меня не работает: Visual Studio для Mac 7.0.1 (сборка 24). Похоже, что должно, но - нет.
IronRod
1
[SuppressMessage("Compiler", "CS4014")]подавляет сообщение в окне списка ошибок, но в окне вывода по-прежнему отображается строка предупреждения
Дэвид Чинг,
35

Мой два способа борьбы с этим.

Сохраните его в переменную сброса (C # 7)

пример

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

С момента появления сбросов в C # 7 я теперь считаю, что это лучше, чем подавление предупреждения. Потому что оно не только подавляет предупреждение, но и проясняет намерение «забей и забудь».

Более того, компилятор сможет оптимизировать его в режиме релиза.

Просто подавь это

#pragma warning disable 4014
...
#pragma warning restore 4014

это достаточно хорошее решение, чтобы «выстрелить и забыть».

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

Если у вас возникли проблемы с запоминанием написания #pragma warning disable 4014, просто позвольте Visual Studio добавить его для вас. Нажмите Ctrl +. открыть «Быстрые действия», а затем «Подавить CS2014»

В целом

Глупо создавать метод, для выполнения которого требуется еще несколько тиков, просто с целью подавления предупреждения.

fjch1997
источник
Это работало в Visual Studio для Mac 7.0.1 (сборка 24).
IronRod
1
Глупо создавать метод, для выполнения которого требуется несколько тиков, просто с целью подавления предупреждения - этот не добавляет лишних тиков вообще, а IMO более [MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
читабелен
1
@Noseratio Много раз, когда я использую AggressiveInliningкомпилятор, по какой-то причине просто игнорирую его
fjch1997
1
Мне нравится опция pragma, потому что она очень проста и применяется только к текущей строке (или секции) кода, а не к целому методу.
wasatchwizard
2
Не забудьте использовать код ошибки, как, #pragma warning disable 4014а затем восстановить предупреждение с помощью #pragma warning restore 4014. Он по-прежнему работает без кода ошибки, но если вы не добавите номер ошибки, он подавит все сообщения.
DunningKrugerEffect
11

Простой способ остановить предупреждение - просто назначить задачу при ее вызове:

Task fireAndForget = WorkAsync(); // No warning now

И так в вашем оригинальном посте вы бы сделали:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
noelicus
источник
Я упомянул этот подход в самом вопросе, как один из тех, которые мне не особенно понравились.
сборник носов
Упс! Не заметил, потому что он был в том же разделе кода, что и ваш прагма ... И я искал ответы. Кроме того, что вам не нравится в этом методе?
noelicus
1
Мне не нравится, что taskвыглядит как забытая локальная переменная. Почти как компилятор должен дать мне другое предупреждение, что-то вроде « taskназначено, но его значение никогда не используется», кроме того, это не так. Кроме того, это делает код менее читабельным. Я сам использую этот подход.
нос
Достаточно справедливо - у меня было похожее чувство, поэтому я и называю это fireAndForget... так что я ожидаю, что отныне оно не будет ссылаться.
noelicus
4

Причиной предупреждения является то, что WorkAsync возвращает значение Task, которое никогда не читается и не ожидается. Вы можете установить тип возврата WorkAsync, voidи предупреждение исчезнет.

Обычно метод возвращает a, Taskкогда вызывающая сторона должна знать статус работника. В случае «забывай и забывай», void должен быть возвращен, чтобы напоминать, что вызывающая сторона независима от вызываемого метода.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
Виктор
источник
2

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

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}
Akli
источник
1

Я нашел этот подход случайно сегодня. Вы можете определить делегата и назначить асинхронный метод делегату первым.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

и называть это так

(new IntermediateHandler(AsyncOperation))();

...

Я подумал, что интересно, что компилятор не будет выдавать точно такое же предупреждение при использовании делегата.

Дэвид Бивон
источник
Не нужно объявлять делегата, вы можете сделать это, (new Func<Task>(AsyncOperation))()хотя IMO все еще слишком многословен.
Носотайо