2015-08-12 3 views
5

Ich habe eine Methode in meiner Ansicht nach ModellUnterschied zwischen den Aufruf eines Asynchron-Methode und Task.Run ein asynchrones Verfahren

private async void SyncData(SyncMessage syncMessage) 
{ 
    if (syncMessage.State == SyncState.SyncContacts) 
    { 
     this.SyncContacts(); 
    } 
} 

private async Task SyncContacts() 
{ 
    foreach(var contact in this.AllContacts) 
    { 
     // do synchronous data analysis 
    } 

    // ... 

    // AddContacts is an async method 
    CloudInstance.AddContacts(contactsToUpload); 
} 

Wenn ich SyncData von den UI-Befehle aufrufen, und ich bin die Synchronisierung über einen großen Teil der Daten UI friert ein. Aber wenn ich SyncContacts mit diesem Ansatz nennen

private void SyncData(SyncMessage syncMessage) 
{ 
    if (syncMessage.State == SyncState.SyncContacts) 
    { 
     Task.Run(() => this.SyncContacts()); 
    } 
} 

Alles ist in Ordnung. Sollten sie nicht gleich sein? Ich dachte, dass nicht mit warten auf den Aufruf einer asynchronen Methode erstellt einen neuen Thread.

+2

'async' bedeutet nicht immer, dass andere Threads beteiligt sind - siehe [es gibt keinen Thread] (http://blog.stephencleary.com/2013/11/there-is-no-thread.html). –

+0

In Ihrem SyncData fehlt das Schlüsselwort await, das den Compiler anweist, mit der anderen Verarbeitung fortzufahren. Im Moment sollte Ihre SyncData-Methode blockieren, was korrekt ist –

Antwort

8

Sollten sie nicht gleich sein? Ich dachte, dass die Verwendung nicht erwarten für Aufruf einer asynchronen Methode erstellt einen neuen Thread.

Nein, async weist keinen magischen neuen Thread für den Methodenaufruf zu. Bei async-await geht es hauptsächlich um die Nutzung natürlich asynchroner APIs, z. B. einen Netzwerkaufruf an eine Datenbank oder einen Remote-Webdienst.

Wenn Sie Task.Run verwenden, verwenden Sie explizit einen Threadpool-Thread, um den Delegaten auszuführen. Wenn Sie eine Methode mit dem Schlüsselwort async markieren, aber nicht intern await, wird sie synchron ausgeführt.

Ich bin mir nicht sicher, was Ihre SyncContacts() Methode tatsächlich tut (da Sie nicht seine Implementierung zur Verfügung gestellt haben), aber es async selbst zu markieren, wird Ihnen nichts bringen.

Edit:

Nachdem Sie nun die Implementierung hinzugefügt haben, sehe ich zwei Dinge:

  1. Ich bin nicht sicher, wie viel CPU-Kapazität ist Ihre synchrone Datenanalyse, aber es kann sein, Genug, damit die Benutzeroberfläche nicht mehr reagiert.
  2. Sie warten nicht auf Ihren asynchronen Betrieb. Es muss wie folgt aussehen:

    private async Task SyncDataAsync(SyncMessage syncMessage) 
    { 
        if (syncMessage.State == SyncState.SyncContacts) 
        { 
         await this.SyncContactsAsync(); 
        } 
    } 
    
    private Task SyncContactsAsync() 
    { 
        foreach(var contact in this.AllContacts) 
        { 
         // do synchronous data analysis 
        } 
    
        // ... 
    
        // AddContacts is an async method 
        return CloudInstance.AddContactsAsync(contactsToUpload); 
    } 
    
+0

Sind Sie sicher, dass es explizit den Thread-Pool verwendet? AFAIK, verwendet es explizit den Standard 'TaskScheduler' und dieses _happens_, um den Thread-Pool zu umhüllen. Wir wissen vielleicht, dass dies der Fall ist, aber die Abstraktionen zielen darauf ab, dieses Detail zu verbergen und garantieren nicht, dass sich dies in der nächsten Version von .net ändern wird. – Gusdor

+4

@Gusdor ['Task.Run'] (http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs89fc01f3bb88eed9) standardmäßig auf [' TaskScheduler.Default'] (http: // referencesource.microsoft.com/#mscorlib/system/threading/Tasks/TaskScheduler.cs,d4a38cb3e3085518), das ist der Taskplaner threadpools. –

+0

@Gusdor, Yuval hat Recht. Von MSDN: "Queues die angegebene Arbeit auf dem ThreadPool ausgeführt wird und gibt eine Aufgabe oder Aufgabe Handle für diese Arbeit zurück." –

0

Was Ihre Linie Task.Run(() => this.SyncContacts()); wirklich tut, ist eine neue Aufgabe zu schaffen es starten und es an den Aufrufer zurückkehrt (die nicht für weitere Zwecke in Ihrem Fall verwendet wird). Das ist der Grund, warum es seine Arbeit im Hintergrund erledigen wird und die Benutzeroberfläche weiter funktionieren wird. Wenn Sie (a) warten müssen, bis die Aufgabe abgeschlossen ist, können Sie await Task.Run(() => this.SyncContacts()); verwenden. Wenn Sie nur sicherstellen möchten, dass SyncContacts beendet ist, wenn Sie Ihre SyncData-Methode zurückgeben, können Sie die wiederkehrende Aufgabe verwenden und am Ende Ihrer SyncData-Methode warten. Wie es in den Kommentaren vorgeschlagen wurde: Wenn Sie nicht interessiert sind, ob die Aufgabe beendet ist oder nicht, können Sie sie einfach zurückgeben.

Microsoft empfiehlt jedoch, Blocking-Code und Async-Code nicht zu mischen, und Async-Methoden enden mit Async (https://msdn.microsoft.com/en-us/magazine/jj991977.aspx). Daher sollten Sie in Betracht ziehen, Ihre Methoden umzubenennen und Methoden nicht mit async zu markieren, wenn Sie das Schlüsselwort await nicht verwenden.

+0

Das ist falsch. 'erwarten' hat nichts damit zu tun, ob etwas im Hintergrund läuft oder nicht. Es hat damit zu tun, wie Sie warten, bis die Aufgabe abgeschlossen ist und wie Sie mit ihrer Antwort umgehen. MS empfiehlt nicht, in jedem Fall 'await' zu verwenden, da dies Overhead verursacht. Wenn eine Methode, die einen Task zurückgibt, nicht auf den Abschluss des Tasks warten muss, gibt es keinen Grund, async/awaward zu verwenden. Geben Sie einfach den Task zurück. –

+1

Ich habe nicht gesagt, dass wait Aufgaben im Hintergrund ausführen würde. Ich sagte, dass Task.Run() es tun wird. Mit der zweiten Sache, Sie haben Recht, sollte ich besser gesagt haben, dass MS empfiehlt, Blocking-Code und Async-Code nicht zu mischen https://msdn.microsoft.com/en-us/magazine/jj991977.aspx. Wie auch immer, eine Aufgabe statt void sollte definitiv durchgeführt werden. – Daniel

0

Nur um zu verdeutlichen, warum die Benutzeroberfläche einfriert - die Arbeit in der engen foreach Schleife ist wahrscheinlich CPU-gebunden und wird den ursprünglichen Thread des Aufrufers blockieren, bis die Schleife abgeschlossen ist.

So, und zwar unabhängig davon, ob die Task von SyncContacts zurückgekehrt ist await ed oder nicht, die CPU gebunden Arbeit vor AddContactsAsync dem Aufruf tritt noch synchron auf und Block, der Thread des Anrufers.

private Task SyncContacts() 
{ 
    foreach(var contact in this.AllContacts) 
    { 
     // ** CPU intensive work here. 
    } 

    // Will return immediately with a Task which will complete asynchronously 
    return CloudInstance.AddContactsAsync(contactsToUpload); 
} 

(Re: warum async/return await auf SyncContacts - siehe Yuval des Punkt - so dass die Methode Asynchron und das Ergebnis erwarten gewesen wasteful in this instance würde)

Für ein WPF-Projekt, sollte es in Ordnung sein zu verwenden, Task.Run, um die CPU-gebundene Arbeit von dem aufrufenden Thread (aber not so for MVC or WebAPI Asp.Net-Projekte) zu tun.

Auch ist die contactsToUpload Kartierungsarbeiten unter der Annahme, Thread-sicher, und dass Ihre App volle Nutzung des Benutzers Ressourcen hat, können Sie auch die Abbildung betrachten Parallelisierung Gesamtausführungszeit zu reduzieren:

var contactsToUpload = this.AllContacts 
    .AsParallel() 
    .Select(contact => MapToUploadContact(contact)); 
    // or simpler, .Select(MapToUploadContact); 
Verwandte Themen