2016-07-27 50 views
-2

Hier ist der UI-Code. Versuch, die Plattenauslastung für in der Listenansicht angezeigte Dateien und Ordner zu berechnen.C# Wie starte ich eine Methode im Hintergrund? (Verwenden Sie Task.Start nicht Async/Warten)

   if (FileHelper.IsFolder(info)) 
       { 
        UsageSummary usage = DiskUsage.GetTotalUsage(info.FullName).Result; 
        totalSize += usage.TotalSize; 
       } 
       else 
       { 
        totalSize += info.Length; 
       } 

Hier ist die GetTotalUsage() -Methode. Die Idee ist, dass es einen zwischengespeicherten Wert zurückgeben sollte, falls verfügbar. Wenn der Wert nicht verfügbar ist, gibt er nur die Größe der Dateien auf der obersten Ebene zurück und startet eine andere Methode, die die Verzeichnisstruktur rekursiv von diesem Punkt abführt und Cache-Einträge erstellt, damit wir das nächste Mal den Ordner aufrufen, den wir bekommen sollten ein zwischengespeicherter Wert

Die letzte Methode ist die Gateway-Methode für die rekursiven Funktionen, die die tatsächliche Größe des Ordners berechnen.

async private static Task AsyncCacheUsage(UsageSummary summary) 
    { 
     try 
     { 
      AccumulateChildFolders(summary.Folder, summary); 

      CacheResults(summary); 

      // Now we start a watch on the volume that 
      // can invalidate cache entries when changes occur. 
      _watcher.WatchVolume(summary.Folder); 

     } 
     catch (Exception ex) 
     { 
      Log.Error(ex, "CacheTotalUsage: {1}", ex.Message); 
     } 
    } 

Der Code scheint zu funktionieren und verhält sich wie erwartet im Debugger.

Aber meine Frage ist, mache ich das richtig? Die GetTotalUsage-Methode wird einmal für jeden Unterordner in dem Ordner aufgerufen, in dem viele Instanzen von AsynchCacheUsage für alle absteigenden verschiedenen Zweige des Dateisystems angezeigt werden. Was bedeutet, dass ich mich um die Sicherheit der Fäden kümmern muss? Der gesamte Code ist statisch, einschließlich des Zugriffs auf ein statisches Wörterbuch, das die Verwendungen zwischenspeichert, denen ich wahrscheinlich eine Sperre hinzufügen sollte. Irgendwelche anderen Fallen, die ich vermisse?

Es scheint komisch, dass ich "AsyncCacheUsage" anrufe, wenn ich wirklich meine, geh und mach deine Arbeit und störe mich nicht.

+1

Ich denke, Sie sollten eine Warnung für AsyncCacheUsage erhalten. Bist du es und ignorierst du es? –

+0

Nichts in 'AsyncCacheUsage macht 'erwarten', deshalb wird es seinen Thread blockieren. Wahrscheinlich sollte "AccumulateChildFolders" ebenfalls async sein, aber Sie müssen vorsichtig sein, wenn Sie ein als Parameter übergebenes Objekt aktualisieren (ich nehme an, Sie aktualisieren den Parameter 'summary' mit Ergebnissen), da dies * nach * der Funktion aktualisiert wird. Lieber einen Rückgabewert verwenden. – Richard

+0

Beginnt die Asynchronität nicht oben in GetTotalUsage? Dort habe ich vor, dass es beginnt. Ich bekomme nicht die Warnung "Ihre Methode wird synchron ausgeführt" vom Compiler. –

Antwort

1

Es scheint komisch, dass ich "AsyncCacheUsage" anrufe, wenn ich wirklich meine, geh und mach deine Arbeit und störe mich nicht.

Das ist nicht, was der gepostete Code tun wird. Es wird AsyncCacheUsage synchron laufen (da es keine await s gibt - wie Damien bemerkte, gibt Ihnen der Compiler eine Warnung, dass AsyncCacheUsage synchron läuft). Dann wird es await, die nie wirklich Kontrolle geben wird.

Der Code, den Sie vereinfacht auf diese geschrieben:

// No async, since there's no await 
private static void AsyncCacheUsage(UsageSummary summary) 
{ 
    try 
    { 
    AccumulateChildFolders(summary.Folder, summary); 
    CacheResults(summary); 
    _watcher.WatchVolume(summary.Folder); 
    } 
    catch (Exception ex) 
    { 
    Log.Error(ex, "CacheTotalUsage: {1}", ex.Message); 
    } 
} 

// No async, since there's no awaits 
public static UsageSummary GetTotalUsage(string folder) 
{ 
    UsageSummary totalUsage = null; 
    if (!IsCached(folder, ref totalUsage)) 
    { 
    totalUsage = GetFileUsage(folder); 
    AsyncCacheUsage(totalUsage); // No await anymore 
    } 
    return totalUsage; 
} 

if (FileHelper.IsFolder(info)) 
{ 
    UsageSummary usage = DiskUsage.GetTotalUsage(info.FullName); 
    totalSize += usage.TotalSize; 
} 
else 
{ 
    totalSize += info.Length; 
} 

Mit anderen Worten, es gibt nichts wirklich asynchron in einem der Code, den Sie geschrieben.

Aber meine Frage ist, mache ich das richtig? Die GetTotalUsage-Methode wird einmal für jeden Unterordner in dem Ordner aufgerufen, in dem viele Instanzen von AsynchCacheUsage für alle absteigenden verschiedenen Zweige des Dateisystems angezeigt werden.

Das ist nicht, was der gebuchte Code tut. Es wird immer synchron ausgeführt, ein Ordner nach dem anderen, alles auf dem aufrufenden Thread.

Das ist wahrscheinlich gut. Die meisten Festplattengeräte sind nicht auf Dutzende oder Hunderte von gleichzeitigen Anfragen verteilt, die auf dem Gerät verteilt sind.

Das heißt, wenn Sie wollen tun, um diese asynchron zu machen, Sie mit Ihrem niedrigsten-Level-API Anrufe beginnen sollte - was auch immer Sie den Aufruf der Festplattennutzung eines Verzeichnisses zu erhalten (vermutlich innerhalb AccumulateChildFolders).Sobald das asynchron ist, können Sie die async von dort wachsen lassen. An einem bestimmten Punkt (wahrscheinlich immer noch innerhalb von AccumulateChildFolders) können Sie await Task.WhenAll verwenden, um mehrere Ordner gleichzeitig zu verarbeiten (wenn Sie möchten).

+0

Es gibt eine Wartezeit in der ersten Prozedur. Das ist wo Intend für die Asynchronität zu beginnen. Vielleicht ist es der Name, der dich rausschmeißt. AsyncCacheUsage kann so viel blockieren, wie es möchte. Die Absicht ist, dass die GetTotal-Verwendung das zwischengespeicherte Ergebnis oder die Approximation der ersten Ebene zurückgeben sollte. Und lasst den Cache-Building-Prozess schleifen. Ich habe es als Prozess benutzt, weil es eine schwere Aufgabe ist, die ich abfeuern und vergessen möchte. –

+1

Ich denke, Sie könnten verwirrt sein, was Async macht. Ohne ein "erwarten" wird es nicht asynchron sein. Wie ich bereits sagte, wird der Compiler Sie warnen, wenn Sie 'async' ohne' await' verwenden, weil es fast sicher ein Fehler ist. –

+0

Rechts. Ich ließ die Warnung weggehen, aber ich tat immer noch nicht ganz, was ich dachte. Ich wusste, dass es richtig war, auf etwas zu warten, auf das ich nicht warten wollte. Ich habe die Warnung befolgt und zu Aufgabe/Start konvertiert. –

0

Also die Antwort ist nein. Ich mache es nicht richtig. Das Warten unterbricht die Ausführung der aufrufenden Methode und gibt das Aufgabenobjekt an das Anrufobjekt an den ursprünglichen Anrufer zurück. Wenn die erwartete Operation abgeschlossen ist, wird die async-Methode fortgesetzt. Der ursprüngliche Aufrufer kann mit der asynchronen Prozedur synchronisieren, indem er die Result-Methode für das Task-Objekt aufruft, das die async-Methode zurückgibt. Ich möchte nicht auf irgendetwas warten. Ich möchte nur eine Hintergrundaufgabe starten.

So sollte mein Code wie folgt aussehen:

auf UI-Thread:

   if (FileHelper.IsFolder(info)) 
       { 
        totalSize += DiskUsage.GetTotalUsage(info.FullName) 
       } 
       else 
       { 
        totalSize += info.Length; 
       } 

Die GetTotalUsage Methode gibt das zwischengespeicherte Ergebnis oder eine der ersten Ebene Annäherung und startet eine Aufgabe, die den Cache bevöl .

public static long GetTotalUsage(string folder) 
    { 
    UsageSummary usage = null; 

    if (!IsCached(folder, ref usage)) 
    { 
     // Not cached, get first-level usage 
     usage = GetFileUsage(folder); 

     try 
      { 
       // Start task to FillCache for this folder. 
       Log.Info("Start FillCache: {0}", folder); 
       var cacheTask = new Task(() => FillCache(usage), TaskCreationOptions.LongRunning); 
       cacheTask.Start(); 
      } 
      catch (Exception ex) 
      { 
       DuLog.Error(ex, "!!! Run cacheTask: {0}", ex.Message); 
      } 

    } 

    // Was cached, or we got the first-level results. 
    // Race condition. FillCache() is updating usage.TotalSize. 
    return usage.TotalSize; 
} 

FillCache ruft AccumulateChildFolders (rekursiv) mit der ersten Ebene insgesamt und speichert das Endergebnis. Zwischenergebnisse werden auf dem Weg zwischengespeichert.

private static Void FillCache(UsageSummary usage) 
    { 
     try 
     { 
      AccumulateChildFolders(summary.Folder, summary); 

      CacheResults(summary); 

      // Now we start a watch on the volume that 
      // can invalidate cache entries when changes occur. 
      _watcher.WatchVolume(summary.Folder); 

     } 
     catch (Exception ex) 
     { 
      Log.Error(ex, "CacheTotalUsage: {1}", ex.Message); 
     } 
    } 
+0

'neue Aufgabe' und' Task.Start' sollten niemals verwendet werden. Ihr älterer Code (mit 'Task.Run') war besser. –