2014-11-05 7 views
27

Ich habe eine SchnittstelleWie implementiert man Interface-Methode, die Task <T> zurückgibt?

interface IFoo 
{ 
    Task<Bar> CreateBarAsync(); 
} 

Es gibt zwei Methoden Bar zu erstellen, eine asynchrone und synchrone ein. Ich möchte für jede dieser beiden Methoden eine Schnittstellenimplementierung bereitstellen.

Für die asynchrone Methode könnte die Umsetzung wie folgt aussehen:

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

Aber wie sollte ich die Klasse Foo2, die die Synchron Methode Bar erstellen verwendet implementieren?

I könnte die Methode implementieren synchron auszuführen:

async Task<Bar> CreateBarAsync() 
    { 
    return SynchronousBarCreator(); 
    } 

Der Compiler wird dann gegen async in der Methodensignatur mit warnen:

Diese async Methode fehlt 'erwarten' Betreiber und wird synchron laufen. Ziehen Sie in Erwägung, den Operator 'abwarten' zu verwenden, um nicht blockierende API-Aufrufe abzuwarten, oder 'warten Sie auf Task.Run (...)', um CPU-gebundene Arbeit an einem Hintergrund-Thread auszuführen.

Oder ich könnte die Methode implementieren explizit Task<Bar> zurückzukehren. Meiner Meinung nach wird der Code dann aussieht weniger lesbar:

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

Aus Performance-Sicht, nehme ich beiden Ansätze etwa den gleichen Kopf hat, oder?

Welchen Ansatz sollte ich wählen? implementieren Sie die async Methode synchron oder explizit den synchronen Methodenaufruf in eine Task?

EDIT

Das Projekt an dem ich arbeite ist wirklich ein .NET-4-Projekt mit async/ Erweiterungen aus dem Microsoft Async NuGet Paket erwarten. Auf .NET 4 kann Task.Run dann durchersetzt werden. Ich habe im obigen Beispiel bewusst die .NET 4.5-Methode verwendet, um die Hauptfrage klarer zu machen.

+2

Für diesen Fall I 'Task.FromResult (SynchronousBarCreator())' glauben ist besser als 'Task.Run (...)', wie es eigentlich nicht planen und eine Aufgabe ausführen um das Ergebnis zu erhalten. –

+0

Läuft 'SynchronBarCreator' lange? Ist es CPU-gebunden oder wartet es auf etwas anderes? – CodesInChaos

+0

In dem Fall, in dem ich gerade arbeite, läuft es nicht lange. Aber es könnte in bevorstehenden Szenarien sein. –

Antwort

16

Wenn Sie ein asynchrones Verfahren von einer Schnittstelle implementieren und Ihre Implementierung ist synchron, man entweder Neds Lösung verwenden:

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

Mit dieser Lösung sieht die Methode Asynchron aber ist synchron.

Oder die Lösung, die Sie vorgeschlagen:

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

Auf diese Weise ist die Methode wirklich async.

Sie verfügen nicht über eine generische Lösung, die alle Fälle von "Wie Implementieren der Schnittstellenmethode, die Task zurückgibt" übereinstimmen. Es hängt vom Kontext ab: Ist Ihre Implementierung schnell genug, so dass sie in einem anderen Thread aufgerufen wird, ist das nutzlos? Wie wird diese Schnittstelle verwendet, wenn diese Methode aufgerufen wird (wird sie die App einfrieren)? Ist es sogar möglich, Ihre Implementierung in einem anderen Thread aufzurufen?

+0

Danke für eine sehr gute Zusammenfassung, Guillaume. Gibt es sogar einen Kontext, in dem Sie meine erste Implementierung empfehlen würden ('async Task CreateBarAsync() {return SynchronousBarCreator();}') oder ist dieser Ansatz * nie * ratsam? –

+11

Sie sollten [* async over sync *] nicht verwenden (http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx) –

+1

'Task.Run' ist fast immer vorbei benutzt. Es gibt wirklich nur einen Grund, es zu benutzen, und das ist der Fall, wenn Sie eine GUI-Anwendung berechnen müssen. Aber es ist sehr selten, rechenintensiv (relativ zur CPU) Aufgaben zu haben. – Aron

18

Try this:

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

Task.FromResult schafft eine bereits abgeschlossene Aufgabe des angegebenen Typs mit dem mitgelieferten Wert verwenden.

+1

Vielen Dank, Ned, das scheint eine sehr gute Lösung für mein Szenario zu sein. Ich suchte auch nach einer Lösung, die funktionieren würde.NET 4 mit * async * -Erweiterungen und zum Glück stellt sich heraus, dass die 'TaskEx'-Klasse auch eine' FromResult'-Methode enthält. Alles in allem bin ich mit dieser Lösung sehr zufrieden. –

+3

Ich bin froh zu helfen –

+0

Hey Leute, warum bewerten Sie diese Antwort? Diese Methode wird synchron ausgeführt, hat jedoch ein Async-Suffix und gibt Task zurück. Dies ist jedoch nicht korrekt. Sie sollten Task.Run (SynchronousBarCreator) zurückgeben, um es asynchron zu machen. –

7

Wenn Sie .NET 4.0, verwenden, können Sie TaskCompletionSource<T> verwenden:

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

Letztendlich, wenn Theres nichts asynchron über Ihre Methode sollten Sie erwägen, einen synchronen Endpunkt aussetzt (CreateBar), die eine neue Bar schafft.Auf diese Weise gibt es keine Überraschungen und keine Notwendigkeit, mit einem redundanten Task zu wickeln.

+0

Vielen Dank, Yuval, auch gut zu wissen. Das würde sogar ohne die * async * -Erweiterungen vom * Microsoft Async * NuGet-Paket funktionieren, oder? Ich schließe jedoch die * async * -Erweiterungen in meinem .NET 4-Projekt ein, und dann scheint die 'TaskEx.FromResult'-Methode der attraktivere Ansatz zu sein. –

+3

Ja, Sie können es ohne Erweiterung verwenden. 'Task.FromResult' und' TaskEx.FromResult' sind einfach ein Wrapper um eine 'TaskCompletionSource' –

+0

Es gibt einen Fall für die Verkettung einer' Task.Run' (oder warten auf eine Task.Delay oder die Verwendung von ContinueWith), wenn diese Methode in der Zukunft asynchron sein - wenn Sie es nicht asynchron ausführen, kann dies zu einer unvorhersehbaren Ausführungsreihenfolge führen oder Rennbedingungen verursachen. –

5

andere Antworten Als Ergänzung gibt es eine weitere Option, die ich Arbeiten mit .NET 4.0 zu glauben:

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

Hinweis task.RunSynchronously(). Es könnte die langsamste Option sein, im Vergleich zu Task<>.FromResult und TaskCompletionSource<>.SetResult, aber es gibt einen feinen, aber wichtigen Unterschied: das Fehlerfortpflanzungsverhalten.

Der obige Ansatz wird das Verhalten der async-Methode nachahmen, bei der die Ausnahme niemals auf denselben Stack-Frame geworfen wird (unrolling out), sondern im Task-Objekt ruhend gespeichert wird. Der Anrufer muss es tatsächlich über await task oder task.Result beobachten, an diesem Punkt wird es erneut geworfen. Diese

ist nicht der Fall mit Task<>.FromResult und TaskCompletionSource<>.SetResult, wo jede Ausnahme ausgelöst durch SynchronousBarCreator wird direkt an den Aufrufer propagiert werden, Abwickeln des Call-Stack.

Ich habe etwas ausführlichere Erläuterung dieser hier:

Any difference between "await Task.Run(); return;" and "return Task.Run()"?

Auf einer Seite zur Kenntnis, schlage ich vor, eine Rückstellung für Stornierung hinzuzufügen, wenn Schnittstellen Gestaltung (auch im Falle einer Stornierung zur Zeit nicht verwendet wird/implementiert):

interface IFoo 
{ 
    Task<Bar> CreateBarAsync(CancellationToken token); 
} 

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

Wirklich gut zu wissen, Naseratio, vielen Dank für diese zusätzliche Information! –

+0

Wenn Sie die Ausnahme vor dem Warten in einer asynchronen Methode auslösen, wird die Ausnahme auf den gleichen Stapel geworfen oder auch gespeichert? – Guillaume

+0

@Guillaume, ja, die Ausnahme wird auch gespeichert, auch wenn sie aus dem synchronen Teil der Async-Methode geworfen wird. – Noseratio

Verwandte Themen