Comment implémenter une méthode d’interface qui renvoie la tâche ?

J’ai une interface

interface IFoo { Task CreateBarAsync(); } 

Il existe deux méthodes pour créer Bar , l’une asynchrone et l’autre synchrone. Je souhaite fournir une implémentation d’interface pour chacune de ces deux méthodes.

Pour la méthode asynchrone, l’implémentation pourrait ressembler à ceci:

 class Foo1 : IFoo { async Task CreateBarAsync() { return await AsynchronousBarCreatorAsync(); } } 

Mais comment dois-je implémenter la classe Foo2 qui utilise la méthode synchrone pour créer Bar ?

Je pourrais implémenter la méthode pour une exécution synchrone:

  async Task CreateBarAsync() { return SynchronousBarCreator(); } 

Le compilateur mettra alors en garde contre l’utilisation async dans la signature de la méthode:

Cette méthode asynchrone manque d’opérateurs “en attente” et s’exécutera de manière synchrone. Envisagez d’utiliser l’opérateur ‘wait’ pour attendre les appels d’API non bloquants ou ‘wait Task.Run (…)’ pour effectuer un travail lié à la CPU sur un thread en arrière-plan.

Ou bien, je pourrais implémenter la méthode pour renvoyer explicitement Task . À mon avis, le code semblera alors moins lisible:

  Task CreateBarAsync() { return Task.Run(() => SynchronousBarCreator()); } 

Du sharepoint vue de la performance, je suppose que les deux approches ont à peu près les mêmes frais généraux, ou?

Quelle approche devrais-je choisir? implémenter la méthode async manière synchrone ou explicitement envelopper l’appel de méthode synchrone dans une Task ?

MODIFIER

Le projet sur lequel je travaille est vraiment un projet .NET 4 avec des extensions async / wait du package Microsoft Async NuGet. Sur .NET 4, Task.Run peut ensuite être remplacé par TaskEx.Run . J’ai consciemment utilisé la méthode .NET 4.5 dans l’exemple ci-dessus dans l’espoir de clarifier la question principale.

Lorsque vous devez implémenter une méthode asynchrone à partir d’une interface et que votre implémentation est synchrone, vous pouvez utiliser la solution de Ned:

 public Task CreateBarAsync() { return Task.FromResult(SynchronousBarCreator()); } 

Avec cette solution, la méthode semble asynchrone mais est synchrone.

Ou la solution que vous avez proposée:

  Task CreateBarAsync() { return Task.Run(() => SynchronousBarCreator()); } 

De cette façon, la méthode est vraiment asynchrone.

Vous n’avez pas de solution générique qui correspond à tous les cas de “Comment implémenter une méthode d’interface renvoyant une tâche”. Cela dépend du contexte: votre implémentation est-elle assez rapide, donc l’invoquer sur un autre thread est inutile? Comment cette interface est-elle utilisée quand cette méthode est-elle appelée (gèlera-t-elle l’application)? Est-il même possible d’appeler votre implémentation dans un autre thread?

Essaye ça:

 class Foo2 : IFoo { public Task CreateBarAsync() { return Task.FromResult(SynchronousBarCreator()); } } 

Task.FromResult crée une tâche déjà terminée du type spécifié en utilisant la valeur fournie.

Si vous utilisez .NET 4.0, vous pouvez utiliser TaskCompletionSource :

 Task CreateBarAsync() { var tcs = new TaskCompletionSource(); tcs.SetResult(SynchronousBarCreator()); return tcs.Task } 

En fin de compte, si votre méthode n’a rien d’asynchrone, vous devez envisager d’exposer un sharepoint terminaison synchrone ( CreateBar ) qui crée une nouvelle Bar . De cette façon, il n’y a pas de sursockets et il n’est pas nécessaire de terminer avec une Task redondante.

Pour compléter les autres réponses, il existe une option supplémentaire, qui, je crois, fonctionne également avec .NET 4.0:

 class Foo2 : IFoo { public Task CreateBarAsync() { var task = new Task(() => SynchronousBarCreator()); task.RunSynchronously(); return task; } } 

Notez task.RunSynchronously() . C’est peut- être l’option la plus lente, comparée à Task<>.FromResult et TaskCompletionSource<>.SetResult , mais il existe une différence subtile mais importante: le comportement de propagation des erreurs .

L’approche ci-dessus imitera le comportement de la méthode async , où une exception n’est jamais levée sur le même cadre de stack (le déroulant), mais est stockée en sumil dans l’object Task . L’appelant doit en fait l’observer via une await task ou une await task task.Result , à quel moment il sera task.Result .

Ce n’est pas le cas avec Task<>.FromResult et TaskCompletionSource<>.SetResult , où toute exception levée par SynchronousBarCreator sera propagée directement à l’appelant, déplaçant ainsi la stack d’appels.

J’ai une explication un peu plus détaillée à ce sujet ici:

Toute différence entre “wait Task.Run (); return;” et “retourner Task.Run ()”?

Sur une note de côté, je suggère d’append une disposition pour l’ annulation lors de la conception d’interfaces (même si l’annulation n’est pas utilisée / implémentée):

 interface IFoo { Task CreateBarAsync(CancellationToken token); } class Foo2 : IFoo { public Task CreateBarAsync(CancellationToken token) { var task = new Task(() => SynchronousBarCreator(), token); task.RunSynchronously(); return task; } }