7

次のような標準の非非同期アクションがあります。

[HttpPost]
public JsonResult StartGeneratePdf(int id)
{
    PdfGenerator.Current.GenerateAsync(id);
    return Json(null);
}

この PDF 生成には長い時間がかかる可能性があることを知っているので、非同期操作の結果を気にせずに、タスクを開始して戻るだけです。

デフォルトの ASP.Net MVC 4 アプリでは、次の素晴らしい例外が発生します。

System.InvalidOperationException: 現時点では非同期操作を開始できません。非同期操作は、非同期ハンドラーまたはモジュール内で、またはページ ライフサイクルの特定のイベント中にのみ開始できます。ページの実行中にこの例外が発生した場合は、ページが <%@ Page Async="true" %> とマークされていることを確認してください。

これは、私のシナリオとはまったく関係ありません。調べてみると、この例外を防ぐためにフラグを false に設定できます。

<appSettings>
    <!-- Allows throwaway async operations from MVC Controller actions -->
    <add key="aspnet:AllowAsyncDuringSyncStages" value="true" />
</appSettings>

https://stackoverflow.com/a/15230973/176877
http://msdn.microsoft.com/en-us/library/hh975440.aspx

しかし問題は、この非同期操作を開始し、同期 MVC コントローラー アクションからそれを忘れることによって何か害があるのでしょうか? 私が見つけることができるすべてのものは、コントローラーを非同期にすることを推奨していますが、それは私が探しているものではありません-常にすぐに戻る必要があるため、意味がありません。

4

3 に答える 3

5

Microsoft 自身が言うように、リラックスしてください ( http://msdn.microsoft.com/en-us/library/system.web.httpcontext.allowasyncduringsyncstages.aspx ):

この動作は、予想されるパターンに適合せず、負の副作用をもたらす可能性のある非同期コードを記述している場合に、早い段階で知らせるためのセーフティ ネットとして意図されています。

いくつかの簡単なルールを覚えておいてください。

  • (非同期であろうとなかろうと) void イベント内で待機しないでください (すぐに返されるため)。一部の WebForms Page イベントは、内部で単純な await をサポートしていますがRegisterAsyncTask、依然として非常に推奨される方法です。

  • async void メソッドを待機しないでください (すぐに返されるため)。

  • .Wait()GUI またはリクエスト スレッド ( 、.Result().WaitAll()、 )内でルート awaitWaitAny()を持たない非同期メソッドで同期的に待機しないでください。または、ルートが で開始されていないか、明示的に指定されていません (GUI としてまたは Request はデッドロックになります)。.ConfigureAwait(false)Task.Run()TaskScheduler.Default

  • すべてのバックグラウンド プロセス、およびすべてのライブラリ メソッドで、同期コンテキストを続行する必要がないものを使用.ConfigureAwait(false)またはTask.Run明示的に指定します。これを「呼び出しスレッド」と考えてください。同じもの)、もはや存在しない可能性さえあります (リクエストがすでに終了している場合)。これだけでも、最も一般的な async/await エラーが回避され、パフォーマンスも向上します。TaskScheduler.Default

マイクロソフトは、あなたがタスクを待つのを忘れたと思っただけです...

更新: Stephen が彼の回答で明確に述べているように (しゃれは意図されていません)、アプリケーション プールを操作する場合、継承しますが、すべての形式のファイア アンド フォーゲットには隠れた危険があります。 、および他のすべてのそのようなメソッドも同様です。リクエストが終了すると、それらは終了することが保証されていません (アプリケーション プールは、さまざまな理由でいつでもリサイクルされる可能性があります)。

それを気にするかどうかはわかりますが(OPの特定のケースのようにビジネスに不可欠でない場合)、常に注意する必要があります。

于 2013-07-07T10:20:55.430 に答える
1

答えはもう少し複雑です。

私の例のように、長時間実行される非同期タスクを設定して戻るだけの場合は、質問で述べたこと以上のことをする必要はありません。

ただし、リスクがあります。誰かが後でこのアクションを拡張し、アクションが非同期であることが理にかなっている場合、その内部のファイア アンド フォーゲット非同期メソッドはランダムに成功または失敗します。こんなふうになります:

  1. ファイア アンド フォーゲット メソッドが終了します。
  2. 非同期タスク内から起動されたため、返されるときにそのタスクのコンテキスト (「マーシャル」) に再結合しようとします。
  3. 非同期コントローラー アクションが完了し、その後コントローラー インスタンスがガベージ コレクションされた場合、そのタスク コンテキストは null になります。

実際に null であるかどうかは、上記のタイミングにより異なります。そうである場合もあれば、そうでない場合もあります。つまり、開発者はすべてが正しく機能していることをテストして確認し、本番環境にプッシュすると爆発します。さらに悪いことに、これが引き起こすエラーは次のとおりです。

  1. NullReferenceException - 非常にあいまいです。
  2. .Net Framework コード内にスローされると、Visual Studio (通常は System.Web.dll) の内部に足を踏み入れることさえできません。
  3. 既存の try/catch コンテキストにマーシャリングできる Task Parallel Library の部分が失敗している部分であるため、try/catch によってキャプチャされません。

そのため、物事が発生しないという謎のエラーが発生します。例外がスローされていますが、それらに関与していない可能性があります。良くない。

これを防ぐためのクリーンな方法は次のとおりです。

[HttpPost]
public JsonResult StartGeneratePdf(int id)
{
    #pragma warning disable 4014 // Fire and forget.
    Task.Run(async () =>
    {
        await PdfGenerator.Current.GenerateAsync(id);
    }).ConfigureAwait(false);

    return Json(null);
}

したがって、ここには問題のない同期コントローラーがありますが、後で非同期に変更しても問題が起こらないようにするために、Run を介して明示的に新しいタスクを開始します。これにより、デフォルトでタスクがメインの ThreadPool に配置されます。それを待っていた場合、それをこのコンテキストに結び付けようとしますが、これは望ましくありません。プラグマ warning disable で警告を無効にします。

于 2013-12-04T23:23:55.337 に答える