待機せずにC#で非同期メソッドを安全に呼び出す方法


322

私が持っている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で「この呼び出しは待機されないため、現在のメソッドは呼び出しが完了する前に引き続き実行されます」という警告が表示されます。その警告のページには、次のように記載されています。

非同期呼び出しの完了を待たず、呼び出されたメソッドが例外を発生させないことが確実な場合にのみ、警告を抑制することを検討する必要があります。

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

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

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

更新:

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


なぜ補完メソッドを作成して、そこで単に無視しないのですか?バックグラウンドスレッドで実行されている場合。その後、プログラムが終了するのを止めることはありません。
Yahya

1
結果を待ちたくない場合は、警告を無視/抑制することが唯一のオプションです。あなたは場合、結果/例外を待ちたいMyAsyncMethod().Wait()
ピーター・リッチー

1
あなたの編集について:それは私には意味がありません。リクエストの1秒後にクライアントに応答が送信され、2秒後に非同期メソッドが例外をスローするとします。その例外をどうしますか?応答がすでに送信されている場合は、クライアントに送信できません。他に何をしますか?

2
@Romokuフェア十分。とにかく、誰かがログを見ると仮定します。:)

2
ASP.NET Web APIシナリオのバリエーションは、長期間のプロセス(Windowsサービスなど)での自己ホスト型 Web APIです。この場合、リクエストにより、時間のかかるバックグラウンドタスクが作成され、高価な処理が行われますが、 HTTP 202(Accepted)で迅速に応答を取得します。
David Rubin

回答:


187

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

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

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

更新:

技術的には、次のようにして同様のことを行うことができますawait

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

... try/ catch(またはusing)を具体的に使用する必要がある場合に便利ですContinueWithが、ConfigureAwait(false)意味を理解する必要があるため、これはもう少し明示的であると思います。


7
これを拡張メソッドに変更しましたTask。}}使用法:MyAsyncMethod()。PerformAsyncTaskWithoutAwait(t => log.ErrorFormat( "MyAsyncMethodの呼び出し中にエラーが発生しました:\ n {0}"、t.Exception));
Mark Avenius 2015年

2
反対投票。コメント?答えに何か問題がある場合は、私は知りたい、および/または修正したいです。
ピーター・リッチー

2
こんにちは-私は反対投票しませんでしたが...あなたの更新について説明できますか?通話は待たれることは想定されていなかったため、そのようにすると、通話を待つだけで、キャプチャされたコンテキストを続行できなくなります...
Bartosz

1
「待機」は、このコンテキストでは正しい説明ではありません。の後の行ConfiguratAwait(false)はタスクが完了するまで実行されませんが、現在のスレッドはそのため「待機」(つまりブロック)しません。次の行は、待機の呼び出しとは非同期で呼び出されます。これがないとConfigureAwait(false)、次の行は元のWeb要求コンテキストで実行されます。これはConfigurateAwait(false)非同期メソッド(タスク)と同じコンテキストで実行され、元のコンテキスト/スレッドを解放して続行します...
Peter Ritchie

15
ContinueWithバージョンは、try {await} catch {}バージョンと同じではありません。最初のバージョンでは、ContinueWith()以降はすべてすぐに実行されます。最初のタスクが実行され、忘れられます。2番目のバージョンでは、catch {}以降はすべて、最初のタスクが完了した後でのみ実行されます。2番目のバージョンは「await MyAsyncMethod()。ContinueWith(t => Console.WriteLine(t.Exception)、TaskContinuationOptions.OnlyOnFaulted).ConfigureAwait(fals);
Thanasis Ioannidis

67

最初にメソッドの作成を検討しGetStringDataasyncそれをawaitから返されるタスクにする必要がありますMyAsyncMethod

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

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

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

更新:

ASP.NETを使用していて、早期に復帰したいので、この件に関する私のブログ投稿が役立つ場合があります。ただし、ASP.NETはこのために設計されておらず、応答が返された後にコードが実行される保証はありませ。ASP.NETは実行できるように最善を尽くしますが、保証はできません。

したがって、これは、イベントをログに放り込むような単純なものに対する優れた解決策であり、ログをあちこちに失うこと重要ではありませ。これは、あらゆる種類のビジネスクリティカルな運用に適したソリューションではありません。これらの状況では、操作(Azureキュー、MSMQなど)を保存する永続的な方法と、それらを処理するための個別のバックグラウンドプロセス(Azureワーカーロール、Win32サービスなど)を備えた、より複雑なアーキテクチャを採用する必要あります。


2
誤解されているかもしれません。例外がスローされて失敗するかどうか気にますが、データを返す前にメソッドを待つ必要はありません。それが何か違いを生むなら、私が働いているコンテキストについての私の編集も見てください。
George Powell

5
@GeorgePowell:アクティブなリクエストなしにASP.NETコンテキストでコードを実行することは非常に危険です。私はあなたを助けるかもしれないブログ投稿を持っていますが、あなたの問題についてもっと知らなければ、私がそのアプローチを勧めるかどうかは言えません。
Stephen Cleary

@StephenCleary同様のニーズがあります。私の例では、クラウドで実行するバッチ処理エンジンが必要です。エンドポイントに「ping」してバッチ処理を開始しますが、すぐに戻りたいです。pingを実行すると開始されるため、そこからすべてを処理できます。スローされた例外がある場合は、その後、彼らはただ...私の「BatchProcessLog /エラー」テーブルに記録されるだろう
ganders

8
C#7では、あなたは置き換えることができvar _ = MyAsyncMethod();_ = MyAsyncMethod();。これにより、CS4014の警告は回避されますが、変数を使用していないことが少し明確になります。
ブライアン

48

Peter Ritchieの答えが私が欲しかったものであり、ASP.NETの初期段階に戻ることに関するStephen Clearyの記事は非常に役に立ちました。

ただし、より一般的な問題(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()


9
Console.ReadLine();少しスリープ/遅延を削除して追加するとMyAsyncMethod、例外が発生することはありません。
チムタム2016年

17

私はこの解決策に終わります:

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

8

これは「ファイアアンドフォーゲット」と呼ばれ、そのための拡張機能があります。

タスクを消費し、それを使用して何もしません。asyncメソッド内でasyncメソッドを呼び出すのに便利です。

nugetパッケージをインストールします

使用する:

MyAsyncMethod().Forget();

12
それは何もしません、それは警告を取り除くためのトリックです。github.com/microsoft/vs-threading/blob/master/src/…を
Paolo Fulgoni

2

問題が発生すると思いますが、なぜこれを行う必要があるのでしょうか。asyncC#5.0 の理由は、結果を待つことができるようにするためです。このメソッドは実際には非同期ではありませんが、現在のスレッドに過度に干渉しないように、一度に呼び出されます。

おそらく、スレッドを開始し、そのままにしてスレッドを終了する方がよいでしょう。


2
async結果を「待つ」だけではありません。「await」は、「await」に続く行が「await」を呼び出したのと同じスレッドで非同期に実行されることを意味します。もちろん、これは「待つ」ことなく行うことができますが、多くのデリゲートが存在し、コードの順次的なルックアンドフィール(usingおよび使用する機能とtry/catch...)を失うことになります
Peter Ritchie

1
@PeterRitchie機能的に非同期で、awaitキーワードを使用せず、キーワードを使用しないメソッドを作成できますが、そのメソッドの定義で使用せずasyncasyncキーワードを使用awaitしてもまったく意味がありません。
2013年

1
@PeterRitchieステートメントから: " asyncは単に結果を"待つ "だけではありません。" asyncキーワード(あなたはそれがバッククォートで囲むことで、キーワードを暗黙のだが)を意味しない、何もより多くの結果を待っているよりも。CSの一般的な概念としての非同期性は、単に結果を待つだけではありません。
2013年

1
@Servy asyncは、非同期メソッド内の待機を管理するステートマシンを作成します。awaitメソッド内にが存在しない場合でも、そのステートマシンは作成されますが、メソッドは非同期ではありません。また、asyncメソッドがを返した場合、void待機するものはありません。だから、それはだよりだけで結果を待っているよりも。
Peter Ritchie

1
@PeterRitchieメソッドがタスクを返す限り、それを待つことができます。ステートマシンやasyncキーワードは必要ありません。あなたがしなければならないことはすべて(そして最後に、その特別なケースでステートマシンを使用して実際に行われること)は、メソッドが同期的に実行され、完了したタスクにラップされることだけです。技術的には、状態マシンを削除するだけではないでしょう。ステートマシンを削除してから呼び出しますTask.FromResult。私はあなた(そしてコンパイラの作者も)が自分で補遺を追加できると思いました。
2013年

0

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

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

このアプローチは、ShowDialogをブロックし、UIの応答性を維持するのと似ています。


0

私はここのパーティーに遅れましたが、他の回答で参照されていない、私が使用している素晴らしいライブラリがあります

https://github.com/brminnick/AsyncAwaitBestPractices

「Fire And Forget」が必要な場合は、タスクの拡張メソッドを呼び出します。

アクションonExceptionを呼び出しに渡すと、例外を適切に処理する機能を維持しながら、実行を待機してユーザーの速度を低下させる必要がなく、両方の長所を確実に得ることができます。

あなたの例では、次のように使用します。

   public string GetStringData()
    {
        MyAsyncMethod().SafeFireAndForget(onException: (exception) =>
                    {
                      //DO STUFF WITH THE EXCEPTION                    
                    }); 
        return "hello world";
    }

それはまた、私のMVVM Xamarinソリューションに最適なICommandを実装する待機可能なAsyncCommandsを提供します


-1

解決策は、sincronizationコンテキストなしで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

あなたが本当にこれをしたいなら。「待機せずにC#で非同期メソッドを呼び出す」に対処するために、内部で非同期メソッドを実行できますTask.Run。このアプローチはMyAsyncMethod完了するまで待機します。

public string GetStringData()
{
    Task.Run(()=> MyAsyncMethod()).Result;
    return "hello world";
}

awaitResultタスクのを非同期でラップ解除しますが、Resultを使用するだけで、タスクが完了するまでブロックされます。


-1

通常、非同期メソッドはTaskクラスを返します。Wait()メソッドまたはResultプロパティを使用し、コードが例外をスローする場合-例外タイプはまとめられますAggregateException- Exception.InnerException正しい例外を見つけるためにクエリを実行する必要があります。

ただし、.GetAwaiter().GetResult()代わりに使用することもできます。非同期タスクも待機しますが、例外はラップされません。

だからここに短い例があります:

public async Task MyMethodAsync()
{
}

public string GetStringData()
{
    MyMethodAsync().GetAwaiter().GetResult();
    return "test";
}

また、非同期関数からいくつかのパラメーターを返すことができるようにしたい場合があります- Action<return type>これは、たとえば次のように、非同期関数に追加を提供することによって実現できます。

public string GetStringData()
{
    return MyMethodWithReturnParameterAsync().GetAwaiter().GetResult();
}

public async Task<String> MyMethodWithReturnParameterAsync()
{
    return "test";
}

非同期メソッドには通常ASync、同じ名前の同期関数間の衝突を回避できるようにするために、名前にサフィックスが付いていることに注意してください。(例FileStream.ReadAsync)-この推奨に従うように関数名を更新しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.