43

.NET 3.5で構築されたAPIサーフェスを持つライブラリを更新中です。結果として、すべてのメソッドは同期的になります。 APIを変更する(つまり、戻り値をTaskに変換する)ことはできません。すべての呼び出し元を変更する必要があるためです。そのため、非同期メソッドを同期的に呼び出す方法が残されています。これは、ASP.NET 4、ASP.NET Core、および.NET / .NET Coreコンソールアプリケーションのコンテキストです。

私は十分にはっきりしていないかもしれません - 状況は私が非同期を意識していない既存のコードを持っていて、非同期メソッドだけをサポートするSystem.Net.HttpとAWS SDKのような新しいライブラリを使いたいということです。そのため、このギャップを埋める必要があり、同期的に呼び出すことはできるが他の場所では非同期メソッドを呼び出すことができるコードを用意することができます。

私はたくさんの読書をしました、そして、これが尋ねられて、答えられたことが何度もあります。

非同期メソッドから非同期メソッドを呼び出す

非同期操作を同期的に待機し、ここでWait()がプログラムをフリーズするのはなぜですか

同期メソッドから非同期メソッドを呼び出す

非同期タスク< T>を実行する方法メソッド同期?

非同期メソッドを同期的に呼び出す

C#で同期メソッドから非同期メソッドを呼び出す方法

問題はほとんどの答えが違うということです。私が見た最も一般的なアプローチは.Resultを使用することですが、これはデッドロックする可能性があります。私は次のことをすべて試してみましたが、うまくいきますが、デッドロックを回避し、優れたパフォーマンスを発揮し、ランタイムとうまく協調する(タスクスケジューラ、タスク作成オプションなどの観点から)最善の方法はわかりません。 )決定的な答えはありますか?最善のアプローチは何ですか?

private static T taskSyncRunner<T>(Func<Task<T>> task)
    {
        T result;
        // approach 1
        result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 2
        result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 3
        result = task().ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 4
        result = Task.Run(task).Result;

        // approach 5
        result = Task.Run(task).GetAwaiter().GetResult();


        // approach 6
        var t = task();
        t.RunSynchronously();
        result = t.Result;

        // approach 7
        var t1 = task();
        Task.WaitAll(t1);
        result = t1.Result;

        // approach 8?

        return result;
    }


  • 答えはあなたがしていないということです。非同期の新しいメソッドを追加して、古い同期のものをレガシー呼び出し元のためにそこに保持します。 - Scott Chamberlain
  • それは少し厳しいように思われ、そして新しいコードを使う能力を本当に殺します。たとえば、新しいバージョンのAWS SDKには非同期以外のメソッドはありません。他の多くの他社製ライブラリも同様です。それで、あなたが世界を書き換えない限り、あなたはこれらのうちのどれも使うことができないのですか? - Erick T
  • オプション8:たぶんTaskCompletionSourceオプションになりますか? - OrdinaryOrange
  • タスクがまだ実行されている間に呼び出されると、resultプロパティがデッドロックを引き起こすだけです。 100%確信できませんが、アプローチ7のように、タスクが終了するまで適切に待機していれば安全です。 - infiniteRefactor
  • 非同期コードから同期APIを使用できない理由のサンプルを表示すると、@scottによる場合よりも優れた回答を見つけることができます。 - Alexei Levenkov

2 답변


58

そのため、非同期メソッドを同期的に呼び出す方法が残されています。

まず、これは問題ありません。これは、具体的なケースを考慮せずに、これを悪魔の行為として包括的な声明として指摘することがStack Overflowで一般的であるためです。

ずっと非同期である必要はありません正当性のために。何かを非同期にブロックして同期させるには、パフォーマンス上のコストがかかります。具体的な場合によります。

デッドロックは、同じシングルスレッド同期コンテキストに同時に入ることを試みる2つのスレッドから発生します。これを回避する手法であれば、ブロッキングによるデッドロックを確実に回避できます。

ここでは、.ConfigureAwait(false)あなたが待っていないので無意味です。

RunSynchronouslyすべてのタスクをそのように処理できるわけではないので、使用は無効です。

.GetAwaiter().GetResult()とは違うResult/Wait()それはそれをまねるawait例外伝播動作あなたはそれが欲しいかどうかを決める必要があります。 (だから、その振る舞いが何であるかを調べてください。ここでそれを繰り返す必要はありません。)

それ以外に、これらすべてのアプローチは同様のパフォーマンスを持っています。彼らは何らかの方法でOSイベントを割り当て、それをブロックします。それは高価な部分です。どの方法が絶対に最も安いかわかりません。

私は個人的に好きですTask.Run(() => DoSomethingAsync()).Wait();それはカテゴリ的にデッドロックを回避するので単純であり、いくつかの例外を隠さないためGetResult()隠れるかもしれません。しかし、あなたは使うことができますGetResult()これと同様に。



20

.NET 3.5で構築されたAPIサーフェスを持つライブラリを更新中です。結果として、すべてのメソッドは同期的になります。 APIを変更する(つまり、戻り値をTaskに変換する)ことはできません。すべての呼び出し元を変更する必要があるためです。そのため、非同期メソッドを同期的に呼び出す方法が残されています。

sync-over-asyncアンチパターンを実行するための普遍的な「最善の」方法はありません。それぞれ独自の欠点があるのは、さまざまなハックだけです。

私がお勧めするのは、古い同期APIをそのまま使用してから、それらと並行して非同期APIを導入することです。あなたはこれを使用して行うことができますBrownfield Asyncに関する私のMSDNの記事で説明されている "boolean argument hack"

最初に、あなたの例の中のそれぞれのアプローチに伴う問題の簡単な説明:

  1. ConfigureAwaitある場合にのみ意味がありますawait;そうでなければ、何もしません。
  2. Resultで例外をラップしますAggregateException;ブロックしなければならない場合は、GetAwaiter().GetResult()代わりに。
  3. Task.Run(明らかに)スレッドプールthreadでそのコードを実行します。これは結構ですその場合に限りコードできるスレッドプールthreadで実行します。
  4. RunSynchronously動的タスクベースの並列処理を行うときに非常にまれな状況で使用される高度なAPIです。あなたはまったくそのシナリオにいません。
  5. Task.WaitAll1つのタスクでWait()
  6. async () => await x効率的ではありません。() => x
  7. 現在のスレッドから開始されたタスクのブロックデッドロックを引き起こす可能性があります

内訳は次のとおりです。

// Problems (1), (3), (6)
result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (1), (3)
result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (1), (7)
result = task().ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (2), (3)
result = Task.Run(task).Result;

// Problems (3)
result = Task.Run(task).GetAwaiter().GetResult();

// Problems (2), (4)
var t = task();
t.RunSynchronously();
result = t.Result;

// Problems (2), (5)
var t1 = task();
Task.WaitAll(t1);
result = t1.Result;

あなたが持っているので、これらのアプローチのいずれかの代わりに、既存の作業同期コード新しい自然に非同期なコードと一緒に使うべきです。たとえば、既存のコードを使用したとします。WebClient

public string Get()
{
  using (var client = new WebClient())
    return client.DownloadString(...);
}

そして、あなたは非同期APIを追加したいのですが、それから私はこのようにそれをするでしょう:

private async Task<string> GetCoreAsync(bool sync)
{
  using (var client = new WebClient())
  {
    return sync ?
        client.DownloadString(...) :
        await client.DownloadStringTaskAsync(...);
  }
}

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();

public Task<string> GetAsync() => GetCoreAsync(sync: false);

または、あなたがしなければならないつかいますHttpClient何らかの理由で:

private string GetCoreSync()
{
  using (var client = new WebClient())
    return client.DownloadString(...);
}

private static HttpClient HttpClient { get; } = ...;

private async Task<string> GetCoreAsync(bool sync)
{
  return sync ?
      GetCoreSync() :
      await HttpClient.GetString(...);
}

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();

public Task<string> GetAsync() => GetCoreAsync(sync: false);

このアプローチでは、あなたの論理は次のようになります。Core同期的または非同期的に実行される可能性があるメソッド(syncパラメータ)。もしsyncですtrueそれからコアメソッドしなければならない既に完了したタスクを返します。実装には、同期APIを使用して同期的に実行し、非同期APIを使用して非同期的に実行します。

最終的には、同期APIを廃止することをお勧めします。


  • あなたはもっとアイテム6を説明することができますか? - Emerson Soares
  • @EmersonSoares:私が説明したとおり非同期イントロ、 あなたはできるawaitそれが返すので、メソッドの結果Taskそれが理由ではないasync。この意味は簡単な方法では、キーワードを削除できます。 - Stephen Cleary
  • 私はasp.net MVC 5 + EF6で同じ問題に最近直面しました。この答えが示すように私はTaskFactoryを使用しましたstackoverflow.com/a/25097498/1683040そして、それは私のためにトリックをしました:)、しかし他のシナリオのために確かではありませんでした。 - LeonardoX
  • あなたの提案を使って理解するWebClient。しかし、Coreメソッドにはどのような利点がありますか。HttpClient?同期メソッドがを直接呼び出すと、よりクリーンで効率的になりません。GetCoreSyncブール値の引数を残すこの方法でハックアウト? - Creepin
  • @クリーピン:はい、そう思います。 - Stephen Cleary

リンクされた質問


関連する質問

最近の質問