2016-04-24 21 views
0

Ich habe einige verschachtelte Async-Methoden aufrufen einander und es ist verwirrend. Ich versuche, ein Projekt zu konvertieren, das die Dateien in einem asynchronen Download herunterlädt. Auf dem Klick auf den Download-Button ist dies die Methode ausgelöst:Verschachtelte Async-Download - Async innerhalb von Async

private async void enableOfflineModeToolStripMenuItem_Click(object sender, EventArgs e) 
     { 
      for(int i = 0; i < _playlists.Count; i++) 
      { 
       DoubleDimList.Add(new List<String>()); 
       for(int j = 0; j < 5; j++) 
       { 
        string sMp3 = IniReadValue(_playlists[i], "Track " + j); 
        DoubleDimList[i].Add(sMp3); 
       } 
       await Task.Run(() => _InetGetHTMLSearchAsyncs(DoubleDimList[i]));    
      } 
     } 

Es schafft eine 2d List, die am Ende wie dieses DoubleDimList[3][20] aussieht. Am Ende von jedem sublist mache ich einen async Download, wie Sie sehen können. Das Verfahren sieht wie folgt aus

private async Task _InetGetHTMLSearchAsyncs(List<string> urlList) 
     { 
      foreach (var url in urlList) 
      { 
       await Task.Run(() => _InetGetHTMLSearchAsync(url)); 
      } 
     } 

die _InetGetHTMLSearchAsync Methode sieht wie folgt aus und ist hier, wo es schwierig wird

private async Task _InetGetHTMLSearchAsync(string sTitle) 
     { 
      Runs++; 
      if (AudioDumpQuery == string.Empty) 
      { 
       //return string.Empty; 
      } 
      string sResearchURL = "http://www.audiodump.biz/music.html?" + AudioDumpQuery + sTitle.Replace(" ", "+"); 
      try 
      { 
       using (var client = new WebClient()) 
       { 
        client.Headers.Add("Referer", @"http://www.audiodump.com/"); 
        client.Headers.Add("user-agent", "Mozilla/5.0(Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14(KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"); 
        client.DownloadStringCompleted += Client_DownloadStringCompleted; 
        await Task.Run(() => client.DownloadStringAsync(new Uri(sResearchURL))); 

       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Debug message: " + ex.Message + "InnerEx: " + ex.StackTrace); 
       Console.WriteLine("Runs: " + Runs); 
       return; 
      } 
     } 

Auf Client_DownloadStringCompleted gibt es eine weitere async Methode aufgerufen. Hier ist es

private async void Client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) 
     { 
      string[] sStringArray; 
      string aRet = e.Result; 
      string[] aTable = _StringBetween(aRet, "<BR><table", "table><BR>", RegexOptions.Singleline); 
      if (aTable != null) 
      { 
       string[] aInfos = _StringBetween(aTable[0], ". <a href=\"", "<a href=\""); 
       if (aInfos != null) 
       { 
        for (int i = 0; i < 1; i++) 
        { 
         sStringArray = aInfos[i].Split('*'); 
         sStringArray[0] = sStringArray[0].Replace("&#39;", "'"); 
         aLinks.Add(sStringArray[0]); 
        } 
        await Task.Run(() => DownloadFile(aLinks[FilesDownloaded])); 
       } 
      } 
     } 

Von dort, Überraschung! Ein weiterer Anruf async.

private async Task DownloadFile(string url) 
     { 
      try 
      { 
       using (var client = new WebClient()) 
       { 
        client.Headers.Add("Referer", @"http://www.audiodump.biz/"); 
        client.Headers.Add("user-agent", "Mozilla/5.0(Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14(KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"); 
        client.DownloadFileCompleted += Client_DownloadFileCompleted; 
        await Task.Run(() => client.DownloadFileTaskAsync(url, mp3Path + "\\" + count + ".mp3")); 

       } 
      } 
      catch (Exception Ex) 
      { 
       Console.WriteLine("File download error: " + Ex.StackTrace); 
      } 
     } 

Nun ist der erste Teil nach der Gründung des 2d List ist die Download-Links der mp3s abzurufen. Der zweite Teil ist das Herunterladen der mp3, sobald eine gültige URL bereitgestellt wurde. Es funktioniert aber auf eine bizarre Weise. Anstatt die Datei normal herunterzuladen (1., 2., 3., ...), lädt sie die Dateien (1., 5., 8. usw.) nach dem Zufallsprinzip herunter.

Es ist mein erster gehen für async Download und Junge, ich bin schon weit von meinen Grenzen.

Wo mache ich das kaputt? Und die Hauptfrage, wird das jemals so funktionieren, wie es funktionieren soll?

+1

Es gibt viel zu viel Code hier, und doch nicht genug. Bitte stellen Sie eine gute [mcve] bereit, die Ihr Problem zuverlässig reproduziert. In der Zwischenzeit werde ich darauf hinweisen, dass die Verwendung von 'Task.Run()' zum Ausführen von 'async'-Methoden im Allgemeinen sinnlos ist. Rufen Sie einfach die Async-Methode direkt auf und warten Sie auf das zurückgegebene Task-Objekt. Sie können eine Gruppe von Async-Aufrufen sammeln, indem Sie die zurückgegebene Task als "IEnumerable " darstellen und dann mit "await Task.WhenAll (...)" auf die gesamte Sammlung warten. –

+0

Wenn Sie die vorhandenen Referenzen für 'async' /' erwarten' sorgfältiger studieren und gute Idiome verwenden, wette ich, dass die meisten Ihrer Probleme einfach verschwinden würden.Beachten Sie jedoch Folgendes: Wenn Sie mehrere Operationen asynchron ausführen, ist es nicht überraschend, dass sie in einer anderen Reihenfolge abgeschlossen werden als in der sie gestartet werden. Das ist _why_ Sie verwenden asynchrone Operationen; Ansonsten können Sie sie auch synchron nacheinander ausführen. –

+0

Sie müssen Ihre asynchronen Methoden nicht in eine Task.Run einbinden. Es wird nicht benötigt und es macht Ihren Code schwieriger zu lesen. Sie können nur warten, bis die Aufgabe Async-Methoden zurückgibt. Der Grund für die unterschiedliche Reihenfolge der Ausführung liegt auch darin, dass Sie den Download in einer Task.Run auslösen, die den Download startet. Dann wird die Methode zurückgegeben und der nächste Download gestartet. Die Reihenfolge, in der das abgeschlossene Ereignis ausgelöst wird, hängt von der Anforderungszeit selbst ab. Wenn Sie die Reihenfolge wirklich beibehalten möchten, sollten Sie stattdessen DownloadStringTaskAsync verwenden. Dadurch wird eine Aufgabe zurückgegeben, auf die Sie für das Ergebnis warten können. –

Antwort

1

Ihr Code sieht ziemlich gut, bis auf zwei Dinge:

  1. Sie nicht Task.Run verwendet werden soll. Der primäre Anwendungsfall für Task.Run ist das Verschieben von CPU-gebundener Arbeit von einem GUI-Thread, sodass die Benutzeroberfläche nicht blockiert wird. Ich habe eine Serie über die proper use of Task.Run, die im Detail darauf eingeht.
  2. Sie sollten ein konsistentes asynchrones Muster verwenden, idealerweise TAP. Ihr Code verwendet derzeit TAP überall außer in _InetGetHTMLSearchAsync, die EAP verwendet. Dies verursacht das seltsame Verhalten, das Sie sehen.

Eine feste _InetGetHTMLSearchAsync etwas würde wie folgt aussehen:

private async Task _InetGetHTMLSearchAsync(string sTitle) 
{ 
    Runs++; 
    string sResearchURL = "http://www.audiodump.biz/music.html?" + AudioDumpQuery + sTitle.Replace(" ", "+"); 
    try 
    { 
    using (var client = new WebClient()) 
    { 
     client.Headers.Add("Referer", @"http://www.audiodump.com/"); 
     client.Headers.Add("user-agent", "Mozilla/5.0(Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14(KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"); 
     string[] sStringArray; 
     string aRet = await client.DownloadStringTaskAsync(new Uri(sResearchURL)); 
     string[] aTable = _StringBetween(aRet, "<BR><table", "table><BR>", RegexOptions.Singleline); 
     if (aTable != null) 
     { 
     string[] aInfos = _StringBetween(aTable[0], ". <a href=\"", "<a href=\""); 
     if (aInfos != null) 
     { 
      for (int i = 0; i < 1; i++) 
      { 
      sStringArray = aInfos[i].Split('*'); 
      sStringArray[0] = sStringArray[0].Replace("&#39;", "'"); 
      aLinks.Add(sStringArray[0]); 
      } 
      await DownloadFile(aLinks[FilesDownloaded]); // Should really be called "DownloadFileAsync" 
     } 
     } 
    } 
    } 
    catch (Exception ex) 
    { 
    Console.WriteLine("Debug message: " + ex.Message + "InnerEx: " + ex.StackTrace); 
    Console.WriteLine("Runs: " + Runs); 
    return; 
    } 
} 
+0

Ich habe 'Task.Run' nicht verwendet, bevor ich hier gepostet habe. VS2015 empfiehlt mir, es zu benutzen, obwohl ich es nicht habe. Deshalb habe ich es getan. Es war wahrscheinlich ein Fehler einer Methode. Danke für die Hilfe. Ich werde auf jeden Fall die obigen Links betrachten. –

+1

@JohnP .: Ich glaube, die Nachricht, die Sie erhalten, ist "Erwägen Sie die Verwendung des Warten-Operators, um nicht blockierende API-Aufrufe abzuwarten, oder' erwarten Task.Run', um CPU-gebundene Arbeit an einem Hintergrund-Thread zu tun ". Und wir machen hier keine CPU-gebundene Arbeit. Sie erhalten diese Nachricht nur dann, wenn Sie async von "outside in" anwenden, was dem natürlichen Ansatz entgegengesetzt ist, dass API-Aufrufe auf der untersten Ebene zuerst verwendet werden und dann von dort aus wachsen. –