Ответ 1
С С# 7 теперь вы можете использовать отбрасывания:
_ = WorkAsync();
Это не дубликат "Как безопасно вызывать метод async на С# без ожидания".
Как я могу подавить следующее предупреждение?
предупреждение CS4014: поскольку этот вызов не ожидается, выполнение текущего метода продолжается до завершения вызова. Рассмотрите возможность применения оператора "ожидание" к результату вызова.
Простой пример:
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
// ...
}
Обновлено, поскольку исходный принятый ответ был отредактирован, я изменил принятый ответ на тот, который использует сброс С# 7.0, так как я не думаю, что ContinueWith
уместен здесь. Всякий раз, когда мне нужно регистрировать исключения для операций "огонь-и-забыть", я использую более сложный подход, предложенный Стивеном Клири здесь.
С С# 7 теперь вы можете использовать отбрасывания:
_ = WorkAsync();
Вы можете создать метод расширения, который предотвратит предупреждение. Метод расширения может быть пустым или вы можете добавить обработку исключений с помощью .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));
}
Вы можете украсить метод следующим атрибутом:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
WorkAsync();
// ...
}
В основном вы говорите компилятору, что знаете, что делаете, и не нужно беспокоиться о возможной ошибке.
Важной частью этого кода является второй параметр. Часть "CS4014:" - это то, что подавляет предупреждение. Вы можете написать все, что захотите, в остальном.
Мой два способа справиться с этим.
Просто подавить его
#pragma warning disable 4014
- достаточно хорошее решение для "стрелять и забывать".
Причина этого предупреждения заключается в том, что во многих случаях вы не намерены использовать метод, возвращающий задачу, не ожидая ее. Подавление предупреждения, когда вы собираетесь стрелять и забывать, имеет смысл.
Если у вас возникли проблемы с запоминанием того, как записать #pragma warning disable 4014
, просто позвольте Visual Studio добавить его для вас. Нажмите Ctrl+. чтобы открыть "Быстрые действия", а затем "Подавить CS2014",
Сохраните его в локальной переменной
пример
var task = Task.Run(() => DoMyStuff()).ConfigureAwait(false);
Поскольку последующего вызова task
, в режиме деблокирования локальная переменная должна быть немедленно оптимизирована, как и никогда. На самом деле, я сомневаюсь, что компилятор даже создаст локальную переменную (кто-то может подтвердить ее декомпилятором).
В общем
Глупо создавать метод, который требует еще нескольких тиков для выполнения, только с целью пресечения предупреждения.
Простым способом остановки предупреждения является просто назначение задачи при вызове:
Task fireAndForget = WorkAsync(); // No warning now
И так в вашем исходном посте вы бы сделали:
static async Task StartWorkAsync()
{
// Fire and forget
var fireAndForget = WorkAsync(); // Tell the compiler you know it a task that being returned
// more unrelated async/await stuff here, e.g.:
// ...
await Task.Delay(2000);
}
Причиной предупреждения является WorkAsync возвращает Task
которая никогда не читается или не ожидается. Вы можете установить для возвращаемого типа WorkAsync значение void
и предупреждение исчезнет.
Обычно метод возвращает Task
когда вызывающему абоненту необходимо знать статус работника. В случае пожара и забывания пустота должна быть возвращена, чтобы она напоминала, что вызывающий объект не зависит от вызываемого метода.
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);
}
Почему бы не обернуть его внутри метода async, который возвращает void? Используется бит, но все переменные используются.
static async Task StartWorkAsync()
{
async void WorkAndForgetAsync() => await WorkAsync();
WorkAndForgetAsync(); // no warning
}
Сегодня я нашел этот подход случайно. Вы можете определить делегата и сначала назначить метод async для делегата.
delegate Task IntermediateHandler();
static async Task AsyncOperation()
{
await Task.Yield();
}
и назовите его так
(new IntermediateHandler(AsyncOperation))();
...
Я подумал, что было интересно, что компилятор не будет давать то же предупреждение при использовании делегата.