Sollte dieser Code eine Task oder Task<object> zurückgeben?
-
21-12-2019 - |
Frage
ich habe gelesen Die Natur von TaskCompletionSource, ein Beitrag von 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;
}
Da ist es uns egal, um welche Art es sich handelt
T
ist, dass ich standardmäßig verwendet habeObject
.Dann, wenn dieAction
erfolgreich ausgeführt wird,SetResult
wird immer noch zum Übergang verwendetTask
in dieRanToCompletion
Endzustand;Da der tatsächliche Ergebniswert jedoch irrelevant ist,null
wird eingesetzt. Endlich,RunAsync
kehrt zurückTask
stattTask<Object>
.Natürlich das instanziiertetask
Der Typ ist immer nochTask<Object>
, aber wir müssen es nicht als solches bezeichnen, und der Verbraucher dieser Methode muss sich nicht um diese Implementierungsdetails kümmern.
Ich verstehe nicht besonders, warum die Methode zurückkehren sollte Task
statt Task<object>
(weshalb ich den fettgedruckten Satz hervorgehoben habe).Ich weiß, dass die Methode auf Rückkehr eingestellt ist Task
Aber tcs
ist ein TaskCompletionSource<Object>
, nicht TaskCompletionSource
(was meiner Meinung nach falsch ist).
Lösung
Es gibt kein Nicht-Generikum TaskCompletionSource
Und wenn man bedenkt, dass alles, was Sie wollen, eine Aufgabe ohne Ergebnis ist, spielt das Ergebnis keine Rolle.Der Anrufer weiß nicht und kümmert sich in diesem Fall nicht darum, dass es sich bei der Aufgabe tatsächlich um eine handelt Task<object>
, Der Anrufer gerade await
s es, und bekommt eine Ausnahme, wenn es eine gibt.Der Anrufer erfährt vom tatsächlichen Ergebnis nichts.
Dies wird natürlich dadurch erleichtert Task<T>
erbt von Task
Es ist auch üblich, eine zu finden Task<bool>
das gibt false zurück, oder Task<int>
mit 0.
Andere Tipps
Es gibt kein Nicht-Generikum TaskCompletionSource
Klasse zum Erstellen von Instanzen von Task
die keine Beispiele dafür sind Task<T>
.Dadurch bleiben zwei Optionen für den generischen Typparameter für übrig TaskCompletionSource<T>
wenn Ihnen der Rückgabewert egal ist (oder Sie ihn nicht angeben):
- Verwenden Sie einen beliebigen vorhandenen Typ, z
object
, als Rückgabetyp.Stellen Sie den Wert auf einnull
um den Abschluss der Aufgabe anzuzeigen. - Verwenden Sie einen bestimmten nicht öffentlichen Typ und legen Sie den Wert auf fest
null
um den Abschluss der Aufgabe anzuzeigen.
Wenn ich ein erstelle TaskCompletionSource<T>
zum Beispiel zum Zweck der Bereitstellung von a Task
Ohne Rückgabewert bevorzuge ich die Verwendung eines dedizierten, nicht öffentlichen Typs, um sicherzustellen, dass der zurückgegebene Code beim Konsumieren nicht verwechselt wird Task
als Beispiel für Task<T>
wo das Ergebnis eine Bedeutung hat.
Zuerst definiere ich die folgende Klasse (es kann eine sein private sealed class
wenn es in einem anderen Typ verschachtelt ist):
internal sealed class VoidResult
{
}
Dann, anstatt zu verwenden TaskCompletionSource<object>
Als Vervollständigungsquelle verwende ich TaskCompletionSource<VoidResult>
.Seit der VoidResult
Auf den Typ kann durch Aufrufen des Codes nicht zugegriffen werden. Der Benutzer kann ihn nicht umwandeln Task
Objekt einer Instanz von Task<VoidResult>
.
Ich verstehe nicht besonders, warum die Methode zurückkehren sollte
Task
stattTask<object>
Denn wenn du zurückkommst Task<Object>
Dies bedeutet, dass diese Methode nach Abschluss einen nützlichen Typwert erzeugt Object
.In diesem Fall erzielen wir kein Ergebnis. Deshalb hat sich Stephen für die Rückkehr entschieden Task
.
Wenn wir uns damit befassen Func<Object>
dann zurück Task<Object>
wäre angebracht, als Func
zu einem Ergebnis führen wird, können wir uns dafür entscheiden, es zurückzugeben.
Warum
TaskCompletionSource<Object>
, nichtTaskCompletionSource
?
Weil es so etwas nicht gibt.Es gibt kein Nicht-Generikum TaskCompletionSource
.
Wenn Sie a zurückgegeben haben Task<object>
, Dann var result = await RunAsync(...)
würde immer wiederkommen null
, da Sie das Ergebnis darauf einstellen.
Dem Kunden ist das egal, also geben Sie einfach eine zurück Task
.
Idealerweise verwenden Sie a TaskCompletionSource
intern, statt a TaskCompletionSource<object>
, und rufen Sie einfach so etwas wie SetCompleted()
anstatt SetResult(null)
.Aber einen solchen Typ gibt es nicht.