60

herehereなどの質問 を発射して忘れる一般的な回答は、async/await を使用するのではなく、代わりに同期メソッドを使用Task.RunまたはTaskFactory.StartNew渡すことです。ただし、起動して忘れ
たいメソッドが非同期であり、同等の同期メソッドがない場合があります。

更新メモ/警告: Stephen Cleary が以下で指摘したように、応答を送信した後に要求の作業を続けるのは危険です。その理由は、その作業がまだ進行中に AppDomain がシャットダウンされる可能性があるためです。詳細については、彼の応答のリンクを参照してください。とにかく、私はそれを最初に指摘したかったので、誰かを間違った道に送ってはいけません.

実際の作業は別のシステム (別のサーバー上の別のコンピューター) によって行われるため、私のケースは有効だと思います。そのため、メッセージがそのシステムに残されたということだけを知る必要があります。例外が発生した場合、サーバーまたはユーザーがそれについてできることは何もなく、ユーザーに影響を与えることはありません。例外ログを参照して手動でクリーンアップする (または自動化されたメカニズムを実装する) だけです。AppDomain がシャットダウンされると、リモート システムに残留ファイルが残りますが、通常のメンテナンス サイクルの一環としてそれを取得します。これは、その存在が Web サーバー (データベース) によって認識されなくなり、その名前が一意であるためです。タイムスタンプが付けられているため、残っている間は問題は発生しません。

Stephen Cleary が指摘したように、永続化メカニズムにアクセスできれば理想的ですが、残念ながら現時点ではありません。

リクエストを開いたままにして、DeleteFoo リクエストがクライアント側 (javascript) で正常に完了したふりをすることを検討しましたが、続行するには応答に情報が必要なため、問題が発生します。

それで、元の質問...

例えば:

//External library
public async Task DeleteFooAsync();

私のasp.net mvcコードでは、DeleteFooAsyncを起動して忘れる方法で呼び出したい-DeleteFooAsyncが完了するのを待って応答を保留したくありません。何らかの理由で DeleteFooAsync が失敗した (または例外をスローした) 場合、ユーザーまたはプログラムがそれに対してできることは何もないので、エラーをログに記録したいだけです。

これで、例外が発生すると監視されない例外が発生することがわかったので、考えられる最も単純なケースは次のとおりです。

//In my code
Task deleteTask = DeleteFooAsync()

//In my App_Start
TaskScheduler.UnobservedTaskException += ( sender, e ) =>
{
    m_log.Debug( "Unobserved exception! This exception would have been unobserved: {0}", e.Exception );
    e.SetObserved();
};

これを行う際にリスクはありますか?

私が考えることができる他のオプションは、次のような独自のラッパーを作成することです。

private void async DeleteFooWrapperAsync()
{
    try
    {
        await DeleteFooAsync();
    }
    catch(Exception exception )
    {
        m_log.Error("DeleteFooAsync failed: " + exception.ToString());
    }
}

そしてそれを TaskFactory.StartNew で呼び出します (おそらく非同期アクションでラップします)。ただし、これは、ファイア アンド フォーゲット方式で非同期メソッドを呼び出すたびに、多くのラッパー コードのように思えます。

私の質問は、非同期メソッドを起動して忘れる方法で呼び出す正しい方法は何ですか?

アップデート:

さて、コントローラーで次のことがわかりました(待機している他の非同期呼び出しがあるため、コントローラーアクションを非同期にする必要があるわけではありません):

[AcceptVerbs( HttpVerbs.Post )]
public async Task<JsonResult> DeleteItemAsync()
{
    Task deleteTask = DeleteFooAsync();
    ...
}

次の形式の例外が発生しました:

未処理の例外: System.NullReferenceException: オブジェクト参照がオブジェクトのインスタンスに設定されていません。System.Web.ThreadContext.AssociateWithCurrentThread (BooleansetImpersonationContext) で

これはここで議論されており、SynchronizationContext と関係があるようであり、「返された Task は、すべての非同期作業が完了する前に終了状態に遷移しました」。

したがって、機能した唯一の方法は次のとおりです。

Task foo = Task.Run( () => DeleteFooAsync() );

これが機能する理由についての私の理解は、StartNew が DeleteFooAsync を処理するための新しいスレッドを取得するためです。

悲しいことに、以下の Scott の提案は、この場合の例外処理には機能しません。foo は、もはや DeleteFooAsync タスクではなく、Task.Run からのタスクであるため、DeleteFooAsync からの例外を処理しないからです。私の UnobservedTaskException は最終的に呼び出されるので、少なくともそれはまだ機能します。

ですから、どうすれば asp.net mvc で非同期メソッドを起動して忘れることができるのでしょうか?

4

3 に答える 3

53

最初に、ASP.NET アプリケーションでは、ほとんどの場合、「ファイア アンド フォーゲット」が誤りであることを指摘しておきます。DeleteFooAsync「ファイア アンド フォーゲット」は、実際に完了するかどうかを気にしない場合にのみ許容されるアプローチです。

この制限を受け入れていただける場合は、私のブログに、タスクを ASP.NET ランタイムに登録するコードがいくつかあります。このコードは、同期作業と非同期作業の両方を受け入れます。

例外をログに記録するための 1 回限りのラッパー メソッドを次のように記述できます。

private async Task LogExceptionsAsync(Func<Task> code)
{
  try
  {
    await code();
  }
  catch(Exception exception)
  {
    m_log.Error("Call failed: " + exception.ToString());
  }
}

そして、BackgroundTaskManager私のブログの を次のように使用します。

BackgroundTaskManager.Run(() => LogExceptionsAsync(() => DeleteFooAsync()));

TaskScheduler.UnobservedTaskExceptionまたは、次のように保持して呼び出すこともできます。

BackgroundTaskManager.Run(() => DeleteFooAsync());
于 2013-08-29T11:14:02.840 に答える