it-roy-ru.com

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

У меня есть метод async, который не возвращает данных:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

Я вызываю это из другого метода, который возвращает некоторые данные:

public string GetStringData()
{
    MyAsyncMethod(); // this generates a warning and swallows exceptions
    return "hello world";
}

Вызов MyAsyncMethod() без ожидания вызывает " поскольку этот вызов не ожидается, текущий метод продолжает выполняться до завершения вызова " в Visual Studio. На странице этого предупреждения говорится:

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

Я уверен, что не хочу ждать завершения звонка; Мне не нужно или у меня нет времени на это. Но вызов может вызвать исключения.

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

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

Обновление:

Для людей, которые предполагают, что я просто жду результата, это код, который отвечает на веб-запрос нашего веб-сервиса (ASP.NET Web API). Ожидание в контексте пользовательского интерфейса сохраняет поток пользовательского интерфейса свободным, но ожидание при вызове веб-запроса будет ожидать завершения задачи, прежде чем отвечать на запрос, что увеличивает время отклика без причины.

256
George Powell

Если вы хотите получить исключение "асинхронно", вы можете сделать:

  MyAsyncMethod().
    ContinueWith(t => Console.WriteLine(t.Exception),
        TaskContinuationOptions.OnlyOnFaulted);

Это позволит вам иметь дело с исключением в потоке, отличном от "основного" потока. Это означает, что вам не нужно "ждать" вызова toMyAsyncMethod() из потока, который вызывает MyAsyncMethod; но все же позволяет вам делать что-то с исключением - но только если происходит исключение.

Обновление:

технически, вы можете сделать что-то подобное с await:

try
{
    await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
    Trace.WriteLine(ex);
}

... что было бы полезно, если бы вам нужно было специально использовать try/catch (или using), но я считаю, что ContinueWith немного более явный, потому что вы должны знать, что ConfigureAwait(false) означает.

159
Peter Ritchie

Сначала вы должны сделать GetStringData методом async и иметь await задачу, возвращенную из MyAsyncMethod.

Если вы абсолютно уверены, что вам не нужно обрабатывать исключения из MyAsyncMethod или знать, когда оно завершится, то вы можете сделать это:

public string GetStringData()
{
  var _ = MyAsyncMethod();
  return "hello world";
}

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

Update:

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

Таким образом, это прекрасное решение для чего-то простого, например, добавления события в журнал, где это не имеет реального значения, если вы потеряете несколько здесь и там. Это не очень хорошее решение для любых критически важных для бизнеса операций. В этих ситуациях вы должны принять более сложную архитектуру с постоянным способом сохранения операций (например, очереди Azure, MSMQ) и отдельным фоновым процессом (например, Azure Worker Роль, Win32 Service) для их обработки.

48
Stephen Cleary

Ответ Питера Ричи был именно тем, что я хотел, и статья Стивена Клири о раннем возвращении в ASP.NET было очень полезно.

Однако в качестве более общей проблемы (не специфичной для контекста ASP.NET) следующее консольное приложение демонстрирует использование и поведение ответа Питера с использованием Task.ContinueWith(...)

static void Main(string[] args)
{
  try
  {
    // output "hello world" as method returns early
    Console.WriteLine(GetStringData());
  }
  catch
  {
    // Exception is NOT caught here
  }
  Console.ReadLine();
}

public static string GetStringData()
{
  MyAsyncMethod().ContinueWith(OnMyAsyncMethodFailed, TaskContinuationOptions.OnlyOnFaulted);
  return "hello world";
}

public static async Task MyAsyncMethod()
{
  await Task.Run(() => { throw new Exception("thrown on background thread"); });
}

public static void OnMyAsyncMethodFailed(Task task)
{
  Exception ex = task.Exception;
  // Deal with exceptions here however you want
}

GetStringData() возвращается рано, не ожидая MyAsyncMethod(), а исключения, сгенерированные в MyAsyncMethod(), обрабатываются в OnMyAsyncMethodFailed(Task task) и не в try/catch вокруг GetStringData()

44
George Powell

Я в конечном итоге с этим решением:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

public string GetStringData()
{
    // Run async, no warning, exception are catched
    RunAsync(MyAsyncMethod()); 
    return "hello world";
}

private void RunAsync(Task task)
{
    task.ContinueWith(t =>
    {
        ILog log = ServiceLocator.Current.GetInstance<ILog>();
        log.Error("Unexpected Error", t.Exception);

    }, TaskContinuationOptions.OnlyOnFaulted);
}
15
Filimindji

Это называется огнем и забудь, и для этого есть расширение .

Потребляет задачу и ничего с ней не делает. Полезно для вызова "забывай и забывай" асинхронных методов внутри асинхронных методов.

Intall пакет nuget .

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

MyAsyncMethod().Forget();
4
wast

Я предполагаю, что возникает вопрос, зачем вам это нужно? Причина для async в C # 5.0 заключается в том, что вы можете ждать результата. Этот метод на самом деле не асинхронный, а просто вызывается за раз, чтобы не слишком сильно мешать текущему потоку.

Возможно, лучше начать поток и оставить его закончить самостоятельно.

2
rhughes

В технологиях с циклами сообщений (не уверен, что ASP является одним из них), вы можете заблокировать цикл и обрабатывать сообщения до тех пор, пока задача не будет завершена, и использовать ContinueWith, чтобы разблокировать код:

public void WaitForTask(Task task)
{
    DispatcherFrame frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false));
    Dispatcher.PushFrame(frame);
}

Этот подход аналогичен блокировке в ShowDialog и по-прежнему сохраняет отзывчивость пользовательского интерфейса.

0
haimb