私には3つのタスクがあります。
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
私のコードを続行する前に、これらすべてを実行する必要があり、それぞれの結果も必要です。結果に共通点はありません
3つのタスクが呼び出されて完了するのを待って結果を取得するにはどうすればよいですか?
私には3つのタスクがあります。
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
私のコードを続行する前に、これらすべてを実行する必要があり、それぞれの結果も必要です。結果に共通点はありません
3つのタスクが呼び出されて完了するのを待って結果を取得するにはどうすればよいですか?
回答:
を使用した後、次を使用しWhenAll
て結果を個別に取り出すことができますawait
。
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
使用することもできますTask.Result
(この時点ですべてが正常に完了していることがわかります)。ただし、他のシナリオでは問題が発生する可能性があるawait
ため、使用することをお勧めしResult
ます。
WhenAll
これを完全に削除できます。awaitsは、タスクがすべて完了するまで、後の3つの割り当てを超えないようにします。
Task.WhenAll()
タスクを並列モードで実行できます。@Servyが削除を提案した理由を理解できません。WhenAll
これらがなければ、1つずつ実行されます
catTask
から返されるまでにはすでに実行されていFeedCat
ます。したがって、どちらのアプローチでも機能します。唯一の問題はawait
、一度に1つずつにするか、すべてまとめて欲しいかです。エラー処理は少し異なります。を使用するとTask.WhenAll
、await
いずれかが早期に失敗しても、すべてが処理されます。
WhenAll
は、操作がいつ実行されるか、またはどのように実行されるかに影響を与えません。これは、唯一の任意の持っている可能性の結果が観察されている方法をもたらすことを。この特定の場合の唯一の違いは、最初の2つのメソッドのいずれかでエラーが発生すると、このコールスタックで例外がスローされ、Stephenのメソッドよりも先にメソッドでスローされます(ただし、同じエラーが常にスローされますが、 )。
ただ、await
それらすべてを開始した後、別々の3つのタスク、。
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
Task.WhenAll
変更を追加すると、プログラムの動作について文字どおり何も観察可能な方法で変更されません。これは純粋に冗長なメソッド呼び出しです。必要に応じて、美的選択として追加することもできますが、コードの動作は変わりません。コードの実行時間は、そのメソッド呼び出しの有無にかかわらず同じです(技術的にはを呼び出すためのオーバーヘッドは非常にわずかですがWhenAll
、これは無視できるはずです)。そのバージョンは、このバージョンよりも実行時間がわずかに長くなります。
WhenAll
は純粋に美的変更です。観察可能な唯一の動作の違いは、前のタスクで障害が発生した場合に、後のタスクが完了するのを待つかどうかです。通常、その必要はありません。ステートメントが真ではない理由についての多数の説明を信じられない場合は、自分でコードを実行して、それが真ではないことを確認できます。
C#7を使用している場合は、次のような便利なラッパーメソッドを使用できます...
public static class TaskEx
{
public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
{
return (await task1, await task2);
}
}
...戻り値の種類が異なる複数のタスクを待機したいときに、このような便利な構文を有効にします。もちろん、待機するタスクの数が異なる場合は、複数のオーバーロードを作成する必要があります。
var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());
ただし、この例を現実のものにしようと考えている場合は、ValueTaskおよび既に完了したタスクに関するいくつかの最適化について、Marc Gravellの回答を参照してください。
Task.WhenAll()
はタプルを返しません。1つはResult
、によって返されたタスクのTask.WhenAll()
完了後に、提供されたタスクのプロパティから構築されています。
.Result
他の人があなたの例をコピーすることによって悪い習慣を永続させるのを避けるために、スティーブンの推論に従って呼び出しを置き換えることをお勧めします。
3つのタスクを考える- FeedCat()
、SellHouse()
およびBuyCar()
、2興味深い例があります。どちらか彼ら(何らかの理由で、おそらくキャッシュまたはエラー)すべての完全な同期、またはそうではありません。
質問から、私たちは持っているとしましょう:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
今、簡単なアプローチは次のようになります:
Task.WhenAll(x, y, z);
しかし...これは結果の処理には不便です。通常は、次のようにawait
します。
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
await Task.WhenAll(x, y, z);
// presumably we want to do something with the results...
return DoWhatever(x.Result, y.Result, z.Result);
}
しかし、これは多くのオーバーヘッドを行い、さまざまな配列(配列を含むparams Task[]
)とリスト(内部)を割り当てます。動作しますが、IMOは素晴らしいものではありません。多くの点で、操作を順番に使用する方が簡単です。async
await
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// do something with the results...
return DoWhatever(await x, await y, await z);
}
上記のコメントの一部とは異なり、await
代わりにTask.WhenAll
を使用しても、タスクの実行方法に(同時、順次など)違いはありません。最高レベルでTask.WhenAll
は、async
/のコンパイラサポートが以前await
からあり、それらが存在しない場合に役立ちました。3つの目立たないタスクではなく、タスクの任意の配列がある場合にも役立ちます。
しかし、私たちはまだasync
/ await
が継続のために多くのコンパイラノイズを生成するという問題を抱えています。タスクが実際に同期的に完了する可能性がある場合は、非同期フォールバックを使用して同期パスを構築することにより、これを最適化できます。
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await x, await y, await z);
}
この「同期パスと非同期フォールバック」アプローチは、特に同期完了が比較的頻繁に行われる高性能コードでますます一般的になっています。完了が常に完全に非同期である場合は、まったく役に立たないことに注意してください。
ここで適用される追加事項:
最近のC#では、一般的なパターンは、async
フォールバックメソッドがローカル関数として一般的に実装されているものです。
Task<string> DoTheThings() {
async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
好みValueTask<T>
にTask<T>
多くの異なる戻り値で、これまでに完全に同期物事の良いチャンスがあれば:
ValueTask<string> DoTheThings() {
async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
ValueTask<Cat> x = FeedCat();
ValueTask<House> y = SellHouse();
ValueTask<Tesla> z = BuyCar();
if(x.IsCompletedSuccessfully &&
y.IsCompletedSuccessfully &&
z.IsCompletedSuccessfully)
return new ValueTask<string>(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
可能であれば、以下を優先IsCompletedSuccessfully
してくださいStatus == TaskStatus.RanToCompletion
。これは.NET Core for Task
、およびどこでもValueTask<T>
Task
結果を使用せずにすべて完了したときにaを返すメソッドでそれらを構成しました。
await
例外はまれではあるが意味があることを前提に、「より良い」例外セマンティクスを取得するには
あなたはそれらをタスクに保存して、それらすべてを待つことができます:
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
Cat cat = await catTask;
House house = await houseTask;
Car car = await carTask;
var catTask = FeedCat()
、関数FeedCat()
を実行して結果を格納せずcatTask
、そのawait Task.WhenAll()
部分を役に立たないものにしますか?
すべてのエラーをログに記録しようとしている場合は、コード内のTask.WhenAll行を確実に保持してください。多くのコメントでは、それを削除して個々のタスクを待つことができます。Task.WhenAllはエラー処理にとって本当に重要です。この行がないと、監視されない例外のためにコードを開いたままにする可能性があります。
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
FeedCatが次のコードで例外をスローするとします。
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
その場合、houseTaskやcarTaskを待つことはありません。ここには3つのシナリオが考えられます。
FeedCatが失敗した場合、SellHouseは既に正常に完了しています。この場合は大丈夫です。
SellHouseは完全ではなく、ある時点で例外が発生して失敗します。例外は発生せず、ファイナライザスレッドで再スローされます。
SellHouseは完全ではなく、内部に待機が含まれています。コードがASP.NETで実行されている場合、SellHouseはその内部でいくつかの待機が完了するとすぐに失敗します。これは、FeedCatが失敗するとすぐに基本的に起動して忘れた呼び出しを行い、同期コンテキストが失われたために発生します。
ケース(3)で発生するエラーは次のとおりです。
System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
at System.Threading.Tasks.Task.Execute()<---
ケース(2)の場合、同様のエラーが発生しますが、元の例外スタックトレースが表示されます。
.NET 4.0以降では、TaskScheduler.UnobservedTaskExceptionを使用して、監視されていない例外をキャッチできます。.NET 4.5以降の場合、監視されない例外はデフォルトで飲み込まれます。.NET4.0の場合、監視されない例外はプロセスをクラッシュさせます。
詳細はこちら:.NET 4.5でのタスク例外処理
スレッドを待機させるかどうかに応じて、Task.WhenAll
前述のように、またはを使用できますTask.WaitAll
。両方の説明については、リンクを参照してください。
Task.WhenAll
結果を使用してから待ちます。
var tCat = FeedCat();
var tHouse = SellHouse();
var tCar = BuyCar();
await Task.WhenAll(tCat, tHouse, tCar);
Cat cat = await tCat;
House house = await tHouse;
Tesla car = await tCar;
//as they have all definitely finished, you could also use Task.Value.
前方警告
このスレッドや他の同様のスレッドにアクセスし、async + await + taskツールセットを使用してEntityFrameworkを並列化する方法を探している人たちに、さっと手に取ってください。:ここに示すパターンは適切ですが、EFの特別なスノーフレークに関しては、関与するすべての* Async()呼び出し内で個別の(新しい)db-context-instanceを使用しない限り、並列実行を実現します。
同じ種類のef-db-contextインスタンスで複数のクエリを並行して実行することを禁止するef-db-contextsの固有の設計上の制限により、このようなことが必要になります。
これは、すでに与えられた回答を利用して、1つ以上のタスクが例外になる場合でも、すべての値を確実に収集する方法です。
public async Task<string> Foobar() {
async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoSomething(await a, await b, await c);
}
using (var carTask = BuyCarAsync())
using (var catTask = FeedCatAsync())
using (var houseTask = SellHouseAsync())
{
if (carTask.Status == TaskStatus.RanToCompletion //triple
&& catTask.Status == TaskStatus.RanToCompletion //cache
&& houseTask.Status == TaskStatus.RanToCompletion) { //hits
return Task.FromResult(DoSomething(catTask.Result, carTask.Result, houseTask.Result)); //fast-track
}
cat = await catTask;
car = await carTask;
house = await houseTask;
//or Task.AwaitAll(carTask, catTask, houseTask);
//or await Task.WhenAll(carTask, catTask, houseTask);
//it depends on how you like exception handling better
return Awaited(catTask, carTask, houseTask);
}
}
ほぼ同じパフォーマンス特性を持つ別の実装は次のとおりです。
public async Task<string> Foobar() {
using (var carTask = BuyCarAsync())
using (var catTask = FeedCatAsync())
using (var houseTask = SellHouseAsync())
{
cat = catTask.Status == TaskStatus.RanToCompletion ? catTask.Result : (await catTask);
car = carTask.Status == TaskStatus.RanToCompletion ? carTask.Result : (await carTask);
house = houseTask.Status == TaskStatus.RanToCompletion ? houseTask.Result : (await houseTask);
return DoSomething(cat, car, house);
}
}
var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());
Catにアクセスする場合は、次のようにします。
var ct = (Cat)dn[0];
これは非常に簡単で、非常に便利です。複雑なソリューションを探す必要はありません。
dynamic
ます。それは悪魔です。これはトリッキーなCOM相互運用機能などのためのものであり、絶対に必要ではない状況では使用しないでください。特にパフォーマンスを気にする場合。またはタイプセーフ。またはリファクタリング。またはデバッグ。