Явно использовать Func <Task> для асинхронной лямбда-функции, когда доступна перегрузка Action
Чтение этого сообщения в блоге о некоторых ошибках С# 5 async/await. Он упоминает в Gotcha # 4 нечто довольно глубокое и о котором я раньше не думал.
Вкратце, он охватывает сценарий, в котором у вас есть метод с двумя перегрузками, который принимает Action
и тот, который принимает Func<Task>
(например, Task.Run
). Эта проблема коренится в аргументе о том, что методы async void
должны использоваться только для обработчиков событий, при этом сообщение затем переходит к изображению следующего сценария. Что делает компилятор, когда с помощью лямбда-функции, такой как следующая, можно скомпилировать оба Func<Task>
и Action
:
Task.Run(async () => {
await Task.Delay(1000);
});
Поскольку Task.Run
имеет подписи как Task.Run(Func<Task>)
, так и Task.Run(Action)
, какой тип представляет собой асинхронную анонимную функцию, скомпилированную? A async void
или Func<Task>
? У меня возникает ощущение, что он скомпилируется до async void
только потому, что его не-общий тип, однако компилятор С# может быть умным и давать предпочтение Func<Task>
.
Также есть способ явно объявить, какую перегрузку я хочу использовать? Я знаю, что могу просто создать новый экземпляр Func<Task>
и передать там функцию async lambda, но он все равно будет скомпилирован до async void
, а затем передаст это в конструктор Func<Task>
. Каков идеальный способ убедиться, что он скомпилирован как Func<Task>
?
Ответы
Ответ 1
Я только что проверил, что он по умолчанию компилируется в 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
вы можете легко это проверить, используя вывод визуального студийного типа, он покажет вам, какой метод он будет скомпилирован, если вы просто наведите указатель мыши на этот метод или просто нажмите метод, нажмите F12, вы увидите метаданные, которые будут сообщать вы, какой тип был выведен компилятором.
Кроме того, есть ли способ явно объявить, какую перегрузку я хочу использовать? Да. Укажите делегата явно.
Task.Run(new Action(async () =>
{
await Task.Delay(1000);
}));
Task.Run(new Func<Task>(async () =>
{
await Task.Delay(1000);
}));
Ответ 2
Поскольку Task.Run
имеет подписи как Task.Run(Func<Task>)
, так и Task.Run(Action)
, какой тип представляет собой асинхронную анонимную функцию, скомпилированную? async void
или Func<Task>
? Я чувствую, что он скомпилируется до async void
только потому, что его не-общий тип, однако С# Compiler может быть умным и давать предпочтение Func<Task>
.
Общее правило, даже без async
, заключается в том, что делегат с возвращаемым типом является лучшим совпадением, чем делегат без возвращаемого типа. Другим примером этого является:
static void Foo(Action a) { }
static void Foo(Func<int> f) { }
static void Bar()
{
Foo(() => { throw new Exception(); });
}
Это однозначно и вызывает вторую перегрузку Foo
.
Также есть способ явно объявить, какую перегрузку я хочу использовать?
Хороший способ сделать это ясным - указать имя параметра. Имена параметров для перегрузок Action
и Func<Task>
отличаются.
Task.Run(action: async () => {
await Task.Delay(1000);
});
Task.Run(function: async () => {
await Task.Delay(1000);
});