web-dev-qa-db-ja.com

Actionオーバーロードが利用可能な場合、非同期ラムダ関数にFunc <Task>を明示的に使用します

C#5のasync/awaitの落とし穴のいくつかに関する this ブログ投稿を読んでください。 Gotcha#4には、非常に奥深いもので、以前は考えもしなかったことが記載されています。

簡単に言えば、Actionを受け取るオーバーロードと_Func<Task>_を受け取るオーバーロード(たとえば_Task.Run_)の2つのオーバーロードを持つメソッドがあるシナリオについて説明します。この問題は、_async void_メソッドはイベントハンドラーにのみ使用されるべきであるという議論に根ざしており、投稿は次のシナリオを描写します-次のようなラムダ関数をコンパイルできる場合、コンパイラは何を推測しますか_Func<Task>_とActionの両方:

_Task.Run(async () => {
  await Task.Delay(1000);
});
_

_Task.Run_にはTask.Run(Func<Task>)Task.Run(Action)の両方のシグネチャがあるため、非同期匿名関数はどの型にコンパイルされますか? _async void_または_Func<Task>_?私の直感では、純粋に_async void_にコンパイルされると言っていますが、これはその非ジェネリック型ですが、C#コンパイラは賢く、_Func<Task>_型を優先させる可能性があるためです。

また、使用するオーバーロードを明示的に宣言する方法はありますか? _Func<Task>_の新しいインスタンスを作成し、そこに非同期ラムダ関数を渡すことができることを知っていますが、それでも_async void_にコンパイルし、それを_Func<Task>_のコンストラクターに渡します。 。 _Func<Task>_としてコンパイルされていることを確認する理想的な方法は何ですか?

38
jduncanator

デフォルトでTask.Run(Func<Task>)にコンパイルされることを確認しましたが、これについては十分な説明がありません。

ここにILの関連部分があります

IL_0001:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0006:  brtrue.s    IL_001B
IL_0008:  ldnull      
IL_0009:  ldftn       UserQuery.<Main>b__0
IL_000F:  newobj      System.Func<System.Threading.Tasks.Task>..ctor//<--Note here
IL_0014:  stsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0019:  br.s        IL_001B
IL_001B:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0020:  call        System.Threading.Tasks.Task.Run

visual Studioの型推論を使用してこれを簡単に確認できます。メソッドの上にマウスを置くか、メソッドを押すだけでコンパイルされるメソッドが表示されます F12 コンパイラによって推測されたタイプが何であるかを示すメタデータを見ることができます。

また、使用するオーバーロードを明示的に宣言する方法はありますか?はい、デリゲートを明示的に指定します。

Task.Run(new Action(async () =>
{
    await Task.Delay(1000);
}));

Task.Run(new Func<Task>(async () =>
{
    await Task.Delay(1000);
}));
27

_Task.Run_にはTask.Run(Func<Task>)Task.Run(Action)の両方のシグネチャがあるため、非同期匿名関数はどの型にコンパイルされますか? _async void_または_Func<Task>_?私の直感では、純粋に_async void_にコンパイルされますが、これは非ジェネリック型ですが、C#コンパイラは賢く、_Func<Task>_型を優先する可能性があるためです。

一般的なルールは、asyncがなくても、戻り値型のデリゲートは戻り値型のないデリゲートよりも優れた一致であるということです。これの別の例は次のとおりです:

_static void Foo(Action a) { }
static void Foo(Func<int> f) { }
static void Bar()
{
  Foo(() => { throw new Exception(); });
}
_

これは明確であり、Fooの2番目のオーバーロードを呼び出します。

また、使用するオーバーロードを明示的に宣言する方法はありますか?

これを明確にする良い方法は、パラメーター名を指定することです。 Actionおよび_Func<Task>_オーバーロードのパラメーター名は異なります。

_Task.Run(action: async () => {
  await Task.Delay(1000);
});
Task.Run(function: async () => {
  await Task.Delay(1000);
});
_
29
user743382