Lecture this article de blog sur certains des pièges de l'asynchronisation/attente de C # 5. Il mentionne dans Gotcha # 4 quelque chose de très profond et auquel je n'avais pas pensé auparavant.
En bref, il couvre le scénario où vous avez une méthode qui a deux surcharges, une qui prend un Action
et une qui prend un Func<Task>
(Par exemple Task.Run
). Ce problème est enraciné dans l'argument selon lequel les méthodes async void
Ne doivent être utilisées que pour les gestionnaires d'événements, la publication décrivant ensuite le scénario suivant - Que déduit le compilateur lorsqu'une fonction lambda comme la suivante peut être compilée à la fois un Func<Task>
et un Action
:
Task.Run(async () => {
await Task.Delay(1000);
});
Étant donné que Task.Run
Possède les signatures de Task.Run(Func<Task>)
et Task.Run(Action)
, à quel type la fonction anonyme asynchrone est-elle compilée? Un async void
Ou un Func<Task>
? Mon intuition dit qu'il se compilera jusqu'à un async void
Uniquement parce que c'est un type non générique, mais le compilateur C # pourrait être intelligent et donner la préférence aux types Func<Task>
.
Existe-t-il également un moyen de déclarer explicitement la surcharge que je souhaite utiliser? Je sais que je pourrais simplement créer une nouvelle instance de Func<Task>
Et y passer la fonction asynchrone lambda, mais elle se compilerait toujours vers un async void
, Puis la transmettrait au constructeur du Func<Task>
. Quelle est la façon idéale de s'assurer que sa compilation est un Func<Task>
?
Je viens de vérifier qu'il est compilé dans Task.Run(Func<Task>)
par défaut, je n'ai pas de bonne explication à cela.
Voici la partie pertinente de l'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
vous pouvez vérifier cela facilement en utilisant l'inférence de type Visual Studio, il vous montrera quelle méthode il sera compilé si vous placez simplement la souris sur la méthode, ou cliquez simplement sur la méthode, appuyez sur F12 vous pouvez voir les métadonnées qui vous diront quel était le type déduit par le compilateur.
Existe-t-il également un moyen de déclarer explicitement la surcharge que je souhaite utiliser? Oui, spécifiez explicitement le délégué.
Task.Run(new Action(async () =>
{
await Task.Delay(1000);
}));
Task.Run(new Func<Task>(async () =>
{
await Task.Delay(1000);
}));
Étant donné que
Task.Run
Possède les signatures deTask.Run(Func<Task>)
etTask.Run(Action)
, à quel type la fonction anonyme asynchrone est-elle compilée? Unasync void
Ou unFunc<Task>
? Mon intuition dit qu'il se compilera jusqu'à unasync void
Uniquement parce que c'est un type non générique, mais le compilateur C # pourrait être intelligent et donner la préférence aux typesFunc<Task>
.
La règle générale, même sans async
, est qu'un délégué avec un type de retour est une meilleure correspondance qu'un délégué sans type de retour. Un autre exemple de ceci est:
static void Foo(Action a) { }
static void Foo(Func<int> f) { }
static void Bar()
{
Foo(() => { throw new Exception(); });
}
Ceci est sans ambiguïté et appelle la deuxième surcharge de Foo
.
Existe-t-il également un moyen de déclarer explicitement la surcharge que je souhaite utiliser?
Une bonne façon de clarifier cela est de spécifier le nom du paramètre. Les noms des paramètres des surcharges Action
et Func<Task>
Sont différents.
Task.Run(action: async () => {
await Task.Delay(1000);
});
Task.Run(function: async () => {
await Task.Delay(1000);
});