Подтвердить что ты не робот

Явно использовать 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>?

4b9b3361

Ответ 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);
});