web-dev-qa-db-ja.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()を呼び出すと、「 この呼び出しは待機されていないため、呼び出しが完了する前に 」という警告が表示されます。その警告のページでそれは述べています:

非同期呼び出しが完了するのを待たずに、呼び出されたメソッドで例外が発生しない場合は、警告を抑制することを検討してください。

電話が完了するのを待ちたくないと思います。私はする必要はないし、する時間もありません。 しかし呼び出しは例外を発生させるかもしれません。

私はこの問題に何度か遭遇しました、そしてそれは共通の解決策を持たなければならない共通の問題であると確信しています。

結果を待たずに安全に非同期メソッドを呼び出すにはどうすればよいですか?

更新:

結果が出るのを待っていると示唆している人達のために、これは私たちのWebサービス(ASP.NET Web API)上のWeb要求に応答しているコードです。 UIコンテキストで待機すると、UIスレッドは解放されたままになりますが、Webリクエスト呼び出しで待機すると、リクエストに応答する前にタスクが終了するのを待つため、理由もなく応答時間が長くなります。

256
George Powell

例外を「非同期的に」取得したい場合は、次のようにします。

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

これにより、「メイン」スレッド以外のスレッドで例外を処理することができます。これは、MyAsyncMethodを呼び出すスレッドからのtoMyAsyncMethod()への呼び出しを「待つ」必要がないことを意味します。しかし、それでも例外が発生した場合に限り、例外を使用して何かをすることができます。

更新:

技術的には、awaitと同様のことができます。

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

... try/catch(またはusing)を特別に使用する必要がある場合、これは役に立ちますが、 ConfigureAwait(false) の意味を知っておく必要があるため、ContinueWithはもう少し明示的にします。

159
Peter Ritchie

最初にGetStringDataasyncメソッドにして、awaitから返されるタスクをMyAsyncMethodにすることを検討する必要があります。

MyAsyncMethodまたはからの例外を処理する必要がないと確信している場合は、いつ完了するかを知ることができます。

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

ところで、これは「一般的な問題」ではありません。 andが正常に完了するかどうかを気にせずに、何らかのコードを実行し、完了したかどうかを気にすることは非常にまれです。

更新:

ASP.NETを使用しており、早期に復帰したいので、私の このテーマに関するブログ投稿が便利 に気付くかもしれません。ただし、ASP.NETはこのために設計されたものではなく、応答が返された後にコードが実行されるという保証はありません。 ASP.NETは実行できるよう最善を尽くしますが、保証することはできません。

ですから、これは、イベントをログに投げ込むような単純なものに対する素晴らしい解決策です。ここでは、いくつかの場所を失っても実際に問題ではありません。これは、あらゆる種類のビジネスクリティカルな運用に適したソリューションではありません。このような状況では、mustにより、操作(Azureキュー、MSMQなど)と個別のバックグラウンドプロセス(Azure Workerロール、Win32サービスなど)を永続的に保存するより複雑なアーキテクチャを採用しますそれらを処理します。

51
Stephen Cleary

Peter Ritchieによる答えは私が欲しかったものでした、そしてASP.NETで早く戻ることについての Stephen Clearyの記事 は非常に役に立ちました。

ただし、より一般的な問題として(ASP.NETコンテキストに限定されない)、次のコンソールアプリケーションは、Task.ContinueWith(...)を使用してPeterの回答の使用方法と動作を示します。

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)で処理され、nottryname __/catchname__周辺の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

これはfire and forgetと呼ばれ、そのための 拡張子 があります。

タスクを消費し、それで何もしません。非同期メソッド内の非同期メソッドへの忘れ去られた呼び出しに役立ちます。

Intall nugetパッケージ

つかいます:

MyAsyncMethod().Forget();
4
wast

私は質問が起こると思います、なぜあなたはこれをする必要があるでしょうか? C#5.0でasyncが使われているのは、結果が出るのを待つためです。このメソッドは実際には非同期ではありませんが、現在のスレッドに干渉しすぎないように一度に呼び出されるだけです。

おそらく、スレッドを開始してそれを終了させるのがよいでしょう。

2
rhughes

解決策は、シンクロナイゼーションコンテキストなしで別の実行タスクでHttpClientを起動することです。

var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);
1
Ernesto Garcia

メッセージループがあるテクノロジでは(ASPがその1つであるかどうかわからない場合)、タスクが終了するまでループをブロックしてメッセージを処理し、ContinueWithを使用してコードのブロックを解除できます。

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

この方法は、ShowDialogをブロックしてもUIを反応させるのに似ています。

0
haimb