2013-03-20 15 views
23

Da es sich bei C# 's Task um eine Klasse handelt, können Sie Task<TDerived> natürlich nicht in eine Task<TBase> umwandeln.Wie konvertiert man eine Aufgabe <TDerived> in eine Aufgabe <TBase>?

Allerdings können Sie tun:

public async Task<TBase> Run() { 
    return await MethodThatReturnsDerivedTask(); 
} 

Gibt es eine statische Aufgabe Methode, die ich anrufen kann eine Task<TDerived> Instanz zu erhalten, die im Wesentlichen nur Punkte auf die zugrunde liegende Aufgabe und wirft das Ergebnis? Ich möchte etwas wie:

public Task<TBase> Run() { 
    return Task.FromDerived(MethodThatReturnsDerivedTask()); 
} 

Gibt es eine solche Methode? Gibt es einen Overhead für die Verwendung einer asynchronen Methode ausschließlich für diesen Zweck?

Antwort

36

Gibt es eine solche Methode?

Nr

Gibt es einen Overhead ein asynchrones Verfahren ausschließlich zu diesem Zweck zu verwenden?

Ja. Aber es ist die einfachste Lösung.

Beachten Sie, dass ein allgemeinerer Ansatz eine Erweiterungsmethode für Task wie Then ist. Stephen Toub erforschte diese in a blog post und ich habe kürzlich incorporated it into AsyncEx.

Mit Then, Ihr Code würde wie folgt aussehen:

public Task<TBase> Run() 
{ 
    return MethodThatReturnsDerivedTask().Then(x => (TBase)x); 
} 

Ein anderer Ansatz mit etwas weniger Aufwand wäre eine eigenen TaskCompletionSource<TBase> zu erstellen und hat es mit dem abgeleiteten Ergebnis abgeschlossen (TryCompleteFromCompletedTask in meiner AsyncEx-Bibliothek):

public Task<TBase> Run() 
{ 
    var tcs = new TaskCompletionSource<TBase>(); 
    MethodThatReturnsDerivedTask().ContinueWith(
     t => tcs.TryCompleteFromCompletedTask(t), 
     TaskContinuationOptions.ExecuteSynchronously); 
    return tcs.Task; 
} 

oder (wenn Sie nicht wollen, eine Abhängigkeit von AsyncEx nehmen):

public Task<TBase> Run() 
{ 
    var tcs = new TaskCompletionSource<TBase>(); 
    MethodThatReturnsDerivedTask().ContinueWith(t => 
    { 
    if (t.IsFaulted) 
     tcs.TrySetException(t.Exception.InnerExceptions); 
    else if (t.IsCanceled) 
     tcs.TrySetCanceled(); 
    else 
     tcs.TrySetResult(t.Result); 
    }, TaskContinuationOptions.ExecuteSynchronously); 
    return tcs.Task; 
} 
+0

Warum nicht einfach 'Rückkehr MethodThatReturnsDerivedTask() ContinueWith (task => (TBase) task.Result, TaskContinuationOptions .ExecuteSynchronously) '? –

+3

Wenn Sie 'Result' verwenden, werden Sie alle Ausnahmen in eine' AggregateException' umbrechen und einen Fehler erzeugen, wenn die ursprüngliche Aufgabe in einem abgebrochenen Zustand abgeschlossen wurde.Das heißt, mit etwas mehr Code, um diese Situationen richtig zu behandeln, könnten Sie eine funktionierende Lösung mit 'ContinueWith' ohne 'TaskCompletionSource' erstellen. –

15

Gibt es eine solche Methode? Gibt es einen Overhead für die Verwendung einer asynchronen Methode ausschließlich für diesen Zweck?

Es gibt keine integrierte Methode dafür, und das verursacht Overhead.

Die "leichteste" Alternative wäre die Verwendung einer TaskCompletionSource<T>, um eine neue Aufgabe dafür zu erstellen. Dies könnte wie so über ein Verlängerungsverfahren erfolgen:

static Task<TBase> FromDerived<TBase, TDerived>(this Task<TDerived> task) where TDerived : TBase 
{ 
    var tcs = new TaskCompletionSource<TBase>(); 

    task.ContinueWith(t => tcs.SetResult(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion); 
    task.ContinueWith(t => tcs.SetException(t.Exception.InnerExceptions), TaskContinuationOptions.OnlyOnFaulted); 
    task.ContinueWith(t => tcs.SetCanceled(), TaskContinuationOptions.OnlyOnCanceled); 

    return tcs.Task; 
} 
+1

Sie sollten den Ausnahmezweig behandeln, indem Sie 'SetException (t.Exception.InnerExceptions) 'ausführen, um' AggregateException'-Wrapper zu vermeiden. –

+0

@StephenCleary Guter Vorschlag - aktualisiert. –

+0

Meinen Sie, TaskContinuationOptions anstelle von TaskCompletionOptions zu verwenden? – Lukazoid

-1

Sie dies versuchen können. task.ContinueWith<TDerived>(t => (TDerived)t.Result);

Verwandte Themen