2013-07-24 5 views
5

Ich habe vor kurzem zum ersten Mal async (und .Net 4.5 wirklich) verwendet, und ich bin auf etwas gestoßen, das mich ratlos hat. Es gibt nicht viele Informationen über die VoidTaskResult-Klasse, die ich im Internet finden kann, also bin ich hergekommen, um zu sehen, ob irgendjemand irgendwelche Ideen über das, was vor sich geht, hat.Was ist der Typ VoidTaskResult in Bezug auf asynchrone Methoden?

Mein Code ist etwas wie das Folgende. Offensichtlich ist dies sehr vereinfacht. Die Grundidee besteht darin, Plugin-Methoden aufzurufen, die asynchron sind. Wenn sie Task zurückgeben, gibt es keinen Rückgabewert aus dem Async-Aufruf. Wenn sie die Aufgabe <> zurückgeben, dann gibt es. Wir wissen nicht im Voraus, um welchen Typ es sich handelt. Daher sollten Sie den Typ des Ergebnisses mithilfe der Reflektion untersuchen (IsGenericType ist wahr, wenn der Typ <> ist) und den Wert mithilfe eines dynamischen Typs abrufen.

In meinem realen Code, rufe ich die Plugin-Methode über Reflexion. Ich denke nicht, dass dies für das Verhalten, das ich sehe, einen Unterschied machen sollte.

// plugin method 
public Task yada() 
{ 
// stuff 
} 

public async void doYada() 
{ 
    Task task = yada(); 
    await task; 

    if (task.GetType().IsGenericType) 
    { 
    dynamic dynTask = task; 
    object result = dynTask.Result; 
    // do something with result 
    } 
} 

Dies funktioniert gut für die oben gezeigte Plugin-Methode. IsGenericType ist falsch (wie erwartet).

Allerdings, wenn Sie die Deklaration des Plugins Methode ändern immer so leicht, IsGenericType jetzt gibt true zurück, und so bricht:

public async Task yada() 
{ 
// stuff 
} 

Wenn Sie dies tun, wird die folgende Ausnahme auf der Linie (Objekt Ergebnis geworfen wird = dynTask.Result;):

RuntimeBinderException

Wenn Sie in das Aufgabenobjekt graben, es scheint tatsächlich Typ zu sein. VoidTaskResult ist ein privater Typ im Threading-Namespace, in dem fast nichts enthalten ist.

VoidTaskResult task

Ich versuchte, meinen Aufruf Code zu ändern:

public async void doYada() 
{ 
    Task task = yada(); 
    await task; 

    if (task.GetType().IsGenericType) 
    { 
    object result = task.GetType().GetProperty("Result").GetMethod.Invoke(task, new object[] { }); 
    // do something with result 
    } 
} 

Diese „erfolgreich“ in dem Sinne, dass es nicht mehr wirft, aber jetzt führen ist vom Typ „VoidTaskResult“, das kann ich nicht vernünftig mach was mit.

Ich sollte hinzufügen, dass es mir schwer fällt, sogar eine echte Frage für all das zu formulieren. Vielleicht ist meine eigentliche Frage etwas wie "Was ist VoidTaskResult?" Oder "Warum passiert dieses komische Ding, wenn ich eine asynchrone Methode dynamisch anrufe?" oder vielleicht sogar "Wie rufst du Plugin-Methoden an, die optional asynchron sind?" Jedenfalls stelle ich das hier in der Hoffnung auf, dass einer der Gurus etwas Licht abwerfen kann.

Antwort

9

Dies liegt an der Art und Weise, wie die Klassenhierarchie um Tasks herum (und insbesondere um Aufgabenvervollständigungsquellen) entworfen wird.

Zunächst einmal Task<T> stammt von Task. Ich nehme an, Sie kennen das schon.

Darüber hinaus können Sie Arten von Task oder Task<T> für Aufgaben erstellen, den Code ausführen. Zum Beispiel, wenn Ihr erstes Beispiel Task.Run oder whatnot zurückgibt, dann würde das ein tatsächliches Objekt Task zurückgeben.

Das Problem tritt auf, wenn Sie in Betracht ziehen, wie TaskCompletionSource<T> mit der Aufgabenhierarchie interagiert.TaskCompletionSource<T> wird verwendet, um Aufgaben zu erstellen, die keinen Code ausführen, sondern als Benachrichtigung dienen, dass ein Vorgang abgeschlossen wurde. Z. B. Timeouts, I/O-Wrapper oder async Methoden.

Es gibt keinen nicht-generic TaskCompletionSource Typen, also, wenn Sie eine Benachrichtigung wie dies ohne einen Rückgabewert haben wollen (zum Beispiel Timeouts oder async Task Methoden), dann müssen Sie für einige T ein TaskCompletionSource<T> schaffen und die Task<T> zurückzukehren. Das async Team musste T für async Task Methoden wählen, also erstellten sie den Typ VoidTaskResult.

Normalerweise ist das kein Problem. Da sich Task<T> von Task ableitet, wird der Wert in Task konvertiert und alle sind glücklich (in der statischen Welt). Jedoch ist jede Aufgabe, die von TaskCompletionSource<T> erstellt wird, tatsächlich vom Typ Task<T>, nicht Task, und Sie sehen dies mit Reflektion/dynamischen Code.

Das Endergebnis ist, dass Sie Task<VoidTaskResult> behandeln müssen, wie es war Task. Jedoch ist VoidTaskResult ein Implementierungsdetail; Das kann sich in Zukunft ändern.

Also, ich empfehle, dass Sie tatsächlich Ihre Logik auf die (deklarierte) Rückgabetyp yada, nicht den (tatsächlichen) Rückgabewert basieren. Dies ahmt genauer nach, was der Compiler macht.

Task task = (Task)yadaMethod.Invoke(...); 
await task; 

if (yadaMethod.ReturnType.IsGenericType) 
{ 
    ... 
} 
+0

Warum führte das Async-Team VoidTaskResult ein, anstatt nur eine nicht generische TaskCompletionSource einzuführen? – Puppy

+0

@Puppy: Ich vermute, weil es viel weniger Arbeit war. Das Schreiben einer nicht generischen 'TaskCompletionSource' ist nicht zu schwer, aber jede öffentliche API muss eine Reihe anderer" Gates "durchlaufen, bevor sie freigegeben werden kann. Sicherheitsüberprüfungen usw. usw. –