20

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

私は、非同期認識ではない既存のコードがあり、非同期メソッドのみをサポートするSystem.Net.HttpやAWS SDKなどの新しいライブラリを使用したいという状況が十分にはっきりしていない可能性があります。だから私はギャップを橋渡しする必要があり、同期的に呼び出すことができるコードを持つことができるが、別の場所で非同期メソッドを呼び出すことができる。

私は多くの読書をしましたが、これは尋ねられ、答えられた回数があります。

Calling async method from non async method

Synchronously waiting for an async operation, and why does Wait() freeze the program here

Calling an async method from a synchronous method

How would I run an async Task<T> method synchronously?

Calling async method synchronously

How to call asynchronous method from synchronous method in C#?

問題はほとんどの回答が異なっていることです!私が見た最も一般的なアプローチはuseResultですが、これはデッドロックする可能性があります。私は以下のすべてを試してみましたが、うまくいきましたが、デッドロックを回避し、パフォーマンスが良く、実行時にうまくいきます(タスクスケジューラ、タスク作成オプションなどを尊重する上で最良の方法はわかりません)。 )。決定的な答えはありますか?最善のアプローチは何ですか?

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; 
    } 
+4

あなたは答えていません。非同期的な新しいメソッドを追加し、レガシーの呼び出し元に対して古い同期メソッドを保持します。 –

+3

これはちょっと厳しいようで、新しいコードを使用する能力を本当になくします。たとえば、AWS SDKの新しいバージョンには、非同期メソッドはありません。他の多くの第三者図書館でも同じです。あなたが世界を書き直さない限り、あなたはこれらのどれも使うことができないのですか? –

+0

オプション8:[TaskCompletionSource](https://msdn.microsoft.com/en-us/library/dd449174(v = vs.110).aspx)がオプションになる可能性がありますか? – OrdinaryOrange

答えて

32

だから私は、同期方法で、どのように最高の呼び出し非同期メソッドを残しています。

まず、これは問題ありません。スタックオーバーフローでは、具体的なケースに関係なくブランケットステートメントとして悪魔の行為としてこれを指摘するのが一般的なので、私はこれを述べています。

については、すべての非同期である必要はありません。同期化するために非同期のものをブロックすることは、パフォーマンス上のコストが問題になるか、まったく関係がないかもしれません。それは具体的なケースに依存します。

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

ここでは、あなたが待っていないので、.ConfigureAwait(false)へのあなたのすべての呼び出しは無意味です。

RunSynchronouslyは、すべてのタスクをそのように処理できるわけではないため、使用することはできません。

.GetAwaiter().GetResult()は、await例外の伝達動作を模倣している点でResult/Wait()とは異なります。あなたはそれを望むかどうかを決める必要があります。 (その行動が何であるかを研究する;それをここで繰り返す必要はない。)

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

私は個人的にはTask.Run(() => DoSomethingAsync()).Wait();パターンが好きです。デッドロックを絶対に避け、シンプルであり、GetResult()が隠している可能性のある例外を隠蔽しないためです。しかし、これにもGetResult()を使うことができます。

+2

ありがとう、これは多くの意味があります。 –

+0

more/cross-ref http://stackoverflow.com/a/22629216/56145 –

10

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

sync-over-asyncのアンチパターンを実行するための普遍的な「最良の」方法はありません。それぞれが独自の欠点を持つ様々なハッキングだけです。

私がお勧めするのは、古い同期APIをそのまま使用して、非同期APIを追加することです。これは"boolean argument hack" as described in my MSDN article on Brownfield Asyncを使用して行うことができます。

まず、あなたの例ではそれぞれのアプローチに伴う問題の簡単な説明:awaitがある場合に

  1. ConfigureAwaitだけ理にかなっています。それ以外の場合は、何もしません。
  2. Resultは、AggregateExceptionで例外をラップします。ブロックする必要がある場合は、代わりにGetAwaiter().GetResult()を使用してください。
  3. Task.Runは、コードをスレッドプールスレッドで実行します(明らかに)。これは、のコードがスレッドプールスレッドで実行されている場合にのみ有効です。
  4. RunSynchronouslyは、動的タスクベースの並列処理を行う際に非常にまれな状況で使用される高度なAPIです。あなたはまったくそのシナリオにいません。
  5. Task.WaitAllは、1つのタスクで、ちょうどと同じです。
  6. async() => await xは、() => xというと効率的ではありません。
  7. 現在のスレッドから開始されたタスクに対するブロックcan cause deadlocks。ここで

が内訳です:代わりに、これらのアプローチのいずれかの

// 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を使用している場合たとえば、:

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を使用する必要がある場合:

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

、あなたは非同期APIを追加したい場合、私はこのようにそれを行うだろう何らかの理由:

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パラメータで決定)。 synctrueの場合、コアメソッドに完了済みのタスクを返す必要があります。実装には、同期APIを使用して同期的に実行し、非同期APIを使用して非同期に実行します。

最終的には、同期APIの非推奨を推奨します。

+0

項目6を詳しく説明できますか? –

+0

@EmersonSoares:私の[async intro](http://blog.stephencleary.com/2012/02/async-and-await.html)で説明しているように、メソッドの結果を返すため、メソッドの結果を「待つ」ことができます'タスク'であり、 '非同期 'ではありません。これは、[些細な方法ではキーワードを削除できる]ことを意味します(http://blog.stephencleary.com/2016/12/eliding-async-await.html)。 –

+0

最近asp.net mvc 5 + EF6で同じ問題が発生しました。私はこの答えがhttps://stackoverflow.com/a/25097498/1683040を示唆してTaskFactoryを使いました。それは私のためのトリックでした:)しかし、他のシナリオについてはわかりません。 – LeonardoX

関連する問題