108

asyncc# の/キーワードを使用した非同期プログラミングのベスト プラクティスをいくつか見つけましたawait(c# 5.0 は初めてです)。

与えられたアドバイスの1つは次のとおりです。

安定性: 同期コンテキストを把握する

... 一部の同期コンテキストは、再入不可でシングルスレッドです。これは、特定の時点でコンテキスト内で実行できる作業単位は 1 つだけであることを意味します。この例は、Windows UI スレッドまたは ASP.NET 要求コンテキストです。これらのシングル スレッド同期コンテキストでは、簡単にデッドロック状態になります。シングルスレッド コンテキストからタスクを生成し、コンテキストでそのタスクを待機すると、待機中のコードがバックグラウンド タスクをブロックしている可能性があります。

public ActionResult ActionAsync()
{
    // DEADLOCK: this blocks on the async task
    var data = GetDataAsync().Result;

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

私が自分で分析しようとすると、メイン スレッドは で新しいものに生成されますMyWebService.GetDataAsync();が、メイン スレッドはそこで待機するため、 で結果を待ちますGetDataAsync().Result。その間、データの準備ができたとします。メインスレッドが継続ロジックを継続せず、文字列の結果を返すのはなぜGetDataAsync()ですか?

上記の例でデッドロックが発生する理由を誰か説明してもらえますか? 私は問題が何であるかについて完全に無知です...

4

5 に答える 5

89

この例を見てください。スティーブンは明確な答えを持っています:

トップレベルのメソッド ( Button1_ClickUIMyController.Get用 / ASP.NET 用)から始めて、次のようになります。

  1. 最上位のメソッド呼び出しGetJsonAsync(UI/ASP.NET コンテキスト内)。

  2. GetJsonAsync呼び出して REST 要求を開始しHttpClient.GetStringAsyncます (まだコンテキスト内)。

  3. GetStringAsyncTaskREST 要求が完了していないことを示すuncompleted を返します。

  4. GetJsonAsyncTaskから返されるを待っていGetStringAsyncます。GetJsonAsyncコンテキストがキャプチャされ、後でメソッドを実行し続けるために使用されます。GetJsonAsyncuncompleted を返します。これは、メソッドが完了していないTaskことを示します。GetJsonAsync

  5. 最上位のメソッドは、Taskによって返された を同期的にブロックしGetJsonAsyncます。これにより、コンテキスト スレッドがブロックされます。

  6. ... 最終的に、REST 要求が完了します。これで、Taskによって返された が完成しGetStringAsyncます。

  7. の継続GetJsonAsyncは実行する準備ができており、コンテキストで実行できるようにコンテキストが利用可能になるのを待ちます。

  8. デッドロック。最上位のメソッドは、コンテキスト スレッドをブロックし、GetJsonAsync完了するGetJsonAsyncのを待っており、完了できるようにコンテキストが解放されるのを待っています。UI の例では、「コンテキスト」は UI コンテキストです。ASP.NET の例では、「コンテキスト」は ASP.NET 要求コンテキストです。このタイプのデッドロックは、いずれかの「コンテキスト」で発生する可能性があります。

読むべきもう 1 つのリンク: Await、UI、およびデッドロック! オーマイ!

于 2013-02-22T10:37:37.090 に答える
27
  • 事実 1:GetDataAsync().Result;によって返されたタスクがGetDataAsync()完了すると実行されますが、その間は UI スレッドがブロックされます。
  • 事実 2: await ( return result.ToString()) の継続は、実行のために UI スレッドのキューに入れられます
  • 事実 3: によって返されたタスクはGetDataAsync()、キューに入れられた継続が実行されると完了します。
  • 事実 4: UI スレッドがブロックされているため、キューに入れられた継続は実行されません (事実 1)。

デッドロック!

デッドロックは、ファクト 1 またはファクト 2 を回避するために提供された代替手段によって打破できます。

  • 1,4は避けてください。UI スレッドをブロックする代わりにvar data = await GetDataAsync()、UI スレッドを実行し続けることができる を使用します。
  • 2,3 は避けてください。await の継続をブロックされていない別のスレッドにキューvar data = Task.Run(GetDataAsync).Resultイングします。これにより、 によって返されたタスク GetDataAsync()を完了できます。

これは、Stephen Toub による記事で非常によく説明されていDelayAsync()ます。

于 2017-05-11T10:01:23.073 に答える
3

もう 1 つの主なポイントは、タスクをブロックしてはならず、デッドロックを防ぐために async を最後まで使用することです。次に、すべて同期ではなく非同期のブロッキングになります。

public async Task<ActionResult> ActionAsync()
{

    var data = await GetDataAsync();

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}
于 2016-04-19T22:51:35.630 に答える
0

私がたどり着いた回避Join策は、結果を求める前にタスクで拡張メソッドを使用することです。

コードは次のようになります。

public ActionResult ActionAsync()
{
  var task = GetDataAsync();
  task.Join();
  var data = task.Result;

  return View(data);
}

結合方法は次のとおりです。

public static class TaskExtensions
{
    public static void Join(this Task task)
    {
        var currentDispatcher = Dispatcher.CurrentDispatcher;
        while (!task.IsCompleted)
        {
            // Make the dispatcher allow this thread to work on other things
            currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
        }
    }
}

このソリューションの欠点を確認するには、ドメインに十分ではありません(ある場合)

于 2018-09-26T09:58:07.000 に答える