この投稿では、問題、解決策、原因を示してみます。
問題
次のようなコードがあるとします。
static async Task<dynamic> Compute(Task<dynamic> inner) { return await Task.Factory.StartNew(async () => await inner); }
ConfigureAwaitが欠落していると思いますか? ハハ!
注意 :別の記事については、ConfigureAwaitに関する質問を省略します。
起源
async / awaitイディオムの前は、Tasks APIを使用する主な方法はTask.Factoryメソッドでした。 多数のオーバーロードを持つStartNew ()。 だから、タスク。 Run ()は、スケジューラ(TaskScheduler)などを省略して、このアプローチを少し簡単にします。
static Task<T> Run<T>(Func<T> inner) { return Task.Run(inner); } static Task<T> RunFactory<T>(Func<T> inner) { return Task.Factory.StartNew(inner); }
上記の例には特に何もありませんが、ここから違いが始まり、主な問題が発生します-多くの人がTask.Run()は軽量のTask.Factory.StartNew()であると考え始めます。
しかし、これはそうではありません!
より明確にするために、例を考えてみましょう。
static Task<T> Compute<T>(Task<T> inner) { return Task.Run(async () => await inner); } static async Task<T> ComputeWithFactory<T>(Task<T> inner) { return await await Task.Factory.StartNew(async () => await inner); }
なに? 二人待ってる? そうです。
それはすべてオーバーロードについてです:
public static Task<TResult> Run<TResult>(Func<Task<TResult>> function) { // code } public Task<TResult> StartNew<TResult>(Func<TResult> function) { // code }
両方のメソッドの戻り値の型がTask <TResult>であるという事実にもかかわらず、 Runの入力パラメーターはFunc <Task <TResult >>です。
async () => await inner
の場合、Task.Runは既製のステートマシンを受け取り(awaitはコードをステートマシンに変換する以外の何物でもないことを知っています)、すべてがタスクに変わります。
StartNewは同じものを取得しますが、TResultは既にTask <Task <T >>になります。
-OK、しかし最初の例がコンパイルエラーでクラッシュしない理由 待っていない?
答えは動的です。
ある記事では 、 動的な仕組みについて説明しました。C#のすべてのステートメントは、コンパイルではなくランタイムを参照する呼び出しサイトになります。 同時に、コンパイラー自体がより多くのメタデータをランタイムに渡そうとします。
Compute ()メソッドはTask <dynamic>を使用して返し、コンパイラーにこれらの呼び出しノードを作成させます。
さらに、これは正しいコードです-実行時の結果はTask <Task <dynamic >>になります。
解決策
それは非常に簡単です: Unwrap ()メソッドを使用する必要があります。
動的でないコードでは、2つの待機の代わりに1つを実行できます。
static async Task<T> ComputeWithFactory<T>(Task<T> inner) { return await Task.Factory.StartNew(async () => await inner).Unwrap(); }
に適用します
static async Task<dynamic> Compute(Task<dynamic> inner) { return await Task.Factory.StartNew(async () => await inner).Unwrap(); }
予想どおり、結果はTask <dynamic>になります。ここでdynamicは、inner'aの正確な戻り値ですが、まだ別のタスクではありません。
結論
Task.Factory.StartNew()には常にUnwrap拡張メソッドを使用します。 これにより、コードがより慣用的になり(呼び出しごとに1つ待つ)、動的なトリックが防止されます。
Task.Run()-通常のコンピューティング用。
Task.Factory.StartNew()+ Unwrap()-TaskSchedulerなどを示す通常の計算用