Ce code doit-il renvoyer une tâche ou une tâche <objet> ?
-
21-12-2019 - |
Question
je lisais La nature de TaskCompletionSource, un article de Stephen Toub.
public static Task RunAsync(Action action)
{
var tcs = new TaskCompletionSource<Object>();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
action();
tcs.SetResult(null);
}
catch(Exception exc) { tcs.SetException(exc); }
});
return tcs.Task;
}
Puisque nous ne nous soucions plus du type de
T
c'est-à-dire que j'ai utilisé par défautObject
.Puis, lorsque leAction
est exécuté avec succès,SetResult
est toujours utilisé pour faire la transitionTask
dans leRanToCompletion
état final ;cependant, puisque la valeur réelle du résultat n'est pas pertinente,null
est utilisé. Enfin,RunAsync
RetourTask
plutôt queTask<Object>
.Bien sûr, l'instanciétask
Le type est toujoursTask<Object>
, mais nous n'avons pas besoin de nous y référer en tant que tel, et le consommateur de cette méthode n'a pas besoin de se soucier de ces détails d'implémentation.
Je ne comprends pas particulièrement pourquoi la méthode devrait revenir Task
plutôt que Task<object>
(c'est pourquoi j'ai souligné la phrase en gras).Je sais que la méthode est prête à revenir Task
mais tcs
est un TaskCompletionSource<Object>
, pas TaskCompletionSource
(ce qui est faux, je pense).
La solution
Il n'y a pas de non générique TaskCompletionSource
et étant donné que tout ce que vous voulez, c'est une tâche sans résultat, le résultat n'a pas d'importance.L'appelant ne sait pas et ne s'en soucie pas dans ce cas, que la tâche est en réalité une Task<object>
, L'appelant vient de await
c'est tout, et obtient une exception s'il y en a une.L'appelant n'est pas au courant du résultat réel.
Ceci est évidemment facilité par le fait que Task<T>
hérite de Task
Il est également courant de trouver un Task<bool>
qui renvoie faux, ou Task<int>
avec 0.
Autres conseils
Il n'y a pas de non-générique TaskCompletionSource
classe pour créer des instances de Task
qui ne sont pas des cas de Task<T>
.Cela laisse deux options pour le paramètre de type générique pour TaskCompletionSource<T>
lorsque vous ne vous souciez pas (ou ne fournissez pas) la valeur de retour :
- Utilisez un type existant arbitraire, tel que
object
, comme type de retour.Définissez la valeur surnull
pour indiquer l'achèvement de la tâche. - Utilisez un type non public spécifique et définissez la valeur sur
null
pour indiquer l'achèvement de la tâche.
Quand je crée un TaskCompletionSource<T>
par exemple dans le but de fournir un Task
sans valeur de retour, je préfère utiliser un type non public dédié pour garantir que le code consommé ne confondra pas le retour Task
comme exemple de Task<T>
où le résultat a un sens.
Tout d'abord, je définis la classe suivante (cela peut être un private sealed class
s'il est imbriqué dans un autre type :
internal sealed class VoidResult
{
}
Ensuite, au lieu d'utiliser TaskCompletionSource<object>
pour la source de complétion, j'utilise TaskCompletionSource<VoidResult>
.Depuis le VoidResult
type n'est pas accessible en appelant le code, l'utilisateur ne pourra pas diffuser le Task
s'opposer à un cas de Task<VoidResult>
.
Je ne comprends pas particulièrement pourquoi la méthode devrait revenir
Task
plutôt queTask<object>
Parce que quand tu reviens Task<Object>
cela signifie que lorsque cette méthode sera terminée, elle produira une valeur utile de type Object
.dans ce cas, nous ne produisons aucun résultat, c'est pourquoi Stephen a choisi de revenir Task
.
Si nous avons affaire à Func<Object>
puis je reviens Task<Object>
serait approprié, comme Func
produira un résultat, nous pouvons choisir de le renvoyer.
Pourquoi
TaskCompletionSource<Object>
, pasTaskCompletionSource
?
Parce qu'il n'y a rien de tel.Il n'y a pas de non générique TaskCompletionSource
.
Si vous avez retourné un Task<object>
, alors var result = await RunAsync(...)
je reviendrais toujours null
, puisque c'est ce sur quoi vous définissez le résultat.
Le client ne s'en soucie pas, alors vous renvoyez simplement un Task
.
Idéalement, vous utiliseriez un TaskCompletionSource
en interne, au lieu d'un TaskCompletionSource<object>
, et appelle simplement quelque chose comme SetCompleted()
au lieu de SetResult(null)
.Mais ce type n’existe pas.