.NET 3.5で構築されたAPIサーフェスを持つライブラリを更新中です。結果として、すべてのメソッドは同期的になります。 APIを変更する(つまり、戻り値をTaskに変換する)ことはできません。すべての呼び出し元を変更する必要があるためです。そのため、非同期メソッドを同期的に呼び出す方法が残されています。これは、ASP.NET 4、ASP.NET Core、および.NET / .NET Coreコンソールアプリケーションのコンテキストです。
私は十分にはっきりしていないかもしれません - 状況は私が非同期を意識していない既存のコードを持っていて、非同期メソッドだけをサポートするSystem.Net.HttpとAWS SDKのような新しいライブラリを使いたいということです。そのため、このギャップを埋める必要があり、同期的に呼び出すことはできるが他の場所では非同期メソッドを呼び出すことができるコードを用意することができます。
私はたくさんの読書をしました、そして、これが尋ねられて、答えられたことが何度もあります。
非同期操作を同期的に待機し、ここでWait()がプログラムをフリーズするのはなぜですか
問題はほとんどの答えが違うということです。私が見た最も一般的なアプローチは.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;
}
そのため、非同期メソッドを同期的に呼び出す方法が残されています。
まず、これは問題ありません。これは、具体的なケースを考慮せずに、これを悪魔の行為として包括的な声明として指摘することがStack Overflowで一般的であるためです。
ずっと非同期である必要はありません正当性のために。何かを非同期にブロックして同期させるには、パフォーマンス上のコストがかかります。具体的な場合によります。
デッドロックは、同じシングルスレッド同期コンテキストに同時に入ることを試みる2つのスレッドから発生します。これを回避する手法であれば、ブロッキングによるデッドロックを確実に回避できます。
ここでは、.ConfigureAwait(false)
あなたが待っていないので無意味です。
RunSynchronously
すべてのタスクをそのように処理できるわけではないので、使用は無効です。
.GetAwaiter().GetResult()
とは違うResult/Wait()
それはそれをまねるawait
例外伝播動作あなたはそれが欲しいかどうかを決める必要があります。 (だから、その振る舞いが何であるかを調べてください。ここでそれを繰り返す必要はありません。)
それ以外に、これらすべてのアプローチは同様のパフォーマンスを持っています。彼らは何らかの方法でOSイベントを割り当て、それをブロックします。それは高価な部分です。どの方法が絶対に最も安いかわかりません。
私は個人的に好きですTask.Run(() => DoSomethingAsync()).Wait();
それはカテゴリ的にデッドロックを回避するので単純であり、いくつかの例外を隠さないためGetResult()
隠れるかもしれません。しかし、あなたは使うことができますGetResult()
これと同様に。
.NET 3.5で構築されたAPIサーフェスを持つライブラリを更新中です。結果として、すべてのメソッドは同期的になります。 APIを変更する(つまり、戻り値をTaskに変換する)ことはできません。すべての呼び出し元を変更する必要があるためです。そのため、非同期メソッドを同期的に呼び出す方法が残されています。
sync-over-asyncアンチパターンを実行するための普遍的な「最善の」方法はありません。それぞれ独自の欠点があるのは、さまざまなハックだけです。
私がお勧めするのは、古い同期APIをそのまま使用してから、それらと並行して非同期APIを導入することです。あなたはこれを使用して行うことができますBrownfield Asyncに関する私のMSDNの記事で説明されている "boolean argument hack"。
最初に、あなたの例の中のそれぞれのアプローチに伴う問題の簡単な説明:
ConfigureAwait
ある場合にのみ意味がありますawait
;そうでなければ、何もしません。Result
で例外をラップしますAggregateException
;ブロックしなければならない場合は、GetAwaiter().GetResult()
代わりに。Task.Run
(明らかに)スレッドプールthreadでそのコードを実行します。これは結構ですその場合に限りコードできるスレッドプールthreadで実行します。RunSynchronously
動的タスクベースの並列処理を行うときに非常にまれな状況で使用される高度なAPIです。あなたはまったくそのシナリオにいません。Task.WaitAll
1つのタスクでWait()
。async () => await x
効率的ではありません。() => x
。内訳は次のとおりです。
// 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を廃止することをお勧めします。
await
それが返すので、メソッドの結果Task
それが理由ではないasync
。この意味は簡単な方法では、キーワードを削除できます。 - Stephen ClearyWebClient
。しかし、Coreメソッドにはどのような利点がありますか。HttpClient
?同期メソッドがを直接呼び出すと、よりクリーンで効率的になりません。GetCoreSync
ブール値の引数を残すこの方法でハックアウト? - Creepin