2013-10-04 21 views
8

EDITAsynchron und parallel das Herunterladen von Dateien

Ich habe den Titel der Frage geändert, um die Frage zu reflektieren Ich hatte aber auch eine Antwort, wie dies leicht zu erreichen.


Ich versuche, die zweite Methode, um es zurück Task<TResult> statt Task wie in der 1. Methode, aber ich erhalte eine Kaskade von Fehlern als Folge zu beheben versuchen.

  • Ich habe return vor await body(partition.Current);
  • wiederum aufgefordert, mich mich unter so hinzugefügt, um eine return-Anweisung hinzufügen return null unter
  • Aber jetzt ist die select-Anweisung beklagt, dass sie die Art Argument aus der Abfrage nicht ableiten können
  • Ich ändere Task.Run zu Task.Run<TResult> aber ohne Erfolg.

Wie kann ich es beheben? Die erste Methode kommt von http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx, die zweite Methode ist die Überladung, die ich versuche zu erstellen.

public static class Extensions 
{ 
    public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body) 
    { 
     return Task.WhenAll(
      from partition in Partitioner.Create(source).GetPartitions(dop) 
      select Task.Run(async delegate 
      { 
       using (partition) 
        while (partition.MoveNext()) 
         await body(partition.Current); 
      })); 
    } 

    public static Task ForEachAsync<T, TResult>(this IEnumerable<T> source, int dop, Func<T, Task<TResult>> body) 
    { 
     return Task.WhenAll(
      from partition in Partitioner.Create(source).GetPartitions(dop) 
      select Task.Run(async delegate 
      { 
       using (partition) 
        while (partition.MoveNext()) 
         await body(partition.Current); 
      })); 
    } 
} 

Anwendungsbeispiel:

Mit dieser Methode würde Ich mag mehrere Dateien parallel und asynchron zum Download:

private async void MainWindow_Loaded(object sender, RoutedEventArgs e) 
{ 
    Artist artist = await GetArtist(); 
    IEnumerable<string> enumerable = artist.Reviews.Select(s => s.ImageUrl); 
    string[] downloadFile = await DownloadFiles(enumerable); 
} 

public static async Task<string[]> DownloadFiles(IEnumerable<string> enumerable) 
{ 
    if (enumerable == null) throw new ArgumentNullException("enumerable"); 
    await enumerable.ForEachAsync(5, s => DownloadFile(s)); 
    // Incomplete, the above statement is void and can't be returned 
} 

public static async Task<string> DownloadFile(string address) 
{ 
    /* Download a file from specified address, 
     * return destination file name on success or null on failure */ 

    if (address == null) 
    { 
     return null; 
    } 

    Uri result; 
    if (!Uri.TryCreate(address, UriKind.Absolute, out result)) 
    { 
     Debug.WriteLine(string.Format("Couldn't create URI from specified address: {0}", address)); 
     return null; 
    } 

    try 
    { 
     using (var client = new WebClient()) 
     { 
      string fileName = Path.GetTempFileName(); 
      await client.DownloadFileTaskAsync(address, fileName); 
      Debug.WriteLine(string.Format("Downloaded file saved to: {0} ({1})", fileName, address)); 
      return fileName; 
     } 
    } 
    catch (WebException webException) 
    { 
     Debug.WriteLine(string.Format("Couldn't download file from specified address: {0}", webException.Message)); 
     return null; 
    } 
} 
+1

Es ist nicht klar, was man erwarten würde. Du gibst eine ganze Folge von 'T'-Werten ein und führst die gleiche Funktion auf beiden aus - welches einzige Ergebnis würdest du erwarten, um aus der' Task 'zurück zu kommen? –

+0

Ich möchte eine Aufgabe bekommen in diesem Fall habe ich ein Beispiel zu meiner Frage hinzugefügt. – Aybe

+0

* "Mit dieser Methode möchte ich mehrere Dateien parallel und asynchron herunterladen" *: 'Parallel.Foreach' ist nicht genug? –

Antwort

23

ich es gelöst und es hier zu veröffentlichen, könnte jemand helfen, die mit gleicher Fehler.

Mein erster Bedarf war ein kleiner Helfer, der schnell Bilder herunterladen würde, aber auch nur die Verbindung abbrechen, wenn der Server nicht schnell reagiert, all dies parallel und asynchron.

Dieser Helfer gibt Ihnen ein Tupel zurück, das den Remote-Pfad, den lokalen Pfad und die Ausnahme enthält, falls eine aufgetreten ist; also ziemlich nützlich, denn es ist immer gut zu wissen, warum fehlerhafte Downloads fehlerhaft sind. Ich denke, ich habe keine der Situationen vergessen, die bei einem Download auftreten können, aber Sie können es gerne kommentieren.

  • Sie geben eine Liste von URLs
  • Sie herunterladen können einen lokalen Dateinamen angeben, wo sie gespeichert werden, wenn nicht ein für Sie
  • Optional kann eine Dauer zum Aufheben eines Download (praktisch erzeugt werden für langsamen oder nicht erreichbar Server)

können Sie nur DownloadFileTaskAsync selbst verwenden oder die ForEachAsync Helfer für parallele und asynchrone Downloads verwenden.

Code mit einem Beispiel dafür, wie es zu benutzen:

private async void MainWindow_Loaded(object sender, RoutedEventArgs e) 
{ 
    IEnumerable<string> enumerable = your urls here; 
    var results = new List<Tuple<string, string, Exception>>(); 
    await enumerable.ForEachAsync(s => DownloadFileTaskAsync(s, null, 1000), (url, t) => results.Add(t)); 
} 

/// <summary> 
///  Downloads a file from a specified Internet address. 
/// </summary> 
/// <param name="remotePath">Internet address of the file to download.</param> 
/// <param name="localPath"> 
///  Local file name where to store the content of the download, if null a temporary file name will 
///  be generated. 
/// </param> 
/// <param name="timeOut">Duration in miliseconds before cancelling the operation.</param> 
/// <returns>A tuple containing the remote path, the local path and an exception if one occurred.</returns> 
private static async Task<Tuple<string, string, Exception>> DownloadFileTaskAsync(string remotePath, 
    string localPath = null, int timeOut = 3000) 
{ 
    try 
    { 
     if (remotePath == null) 
     { 
      Debug.WriteLine("DownloadFileTaskAsync (null remote path): skipping"); 
      throw new ArgumentNullException("remotePath"); 
     } 

     if (localPath == null) 
     { 
      Debug.WriteLine(
       string.Format(
        "DownloadFileTaskAsync (null local path): generating a temporary file name for {0}", 
        remotePath)); 
      localPath = Path.GetTempFileName(); 
     } 

     using (var client = new WebClient()) 
     { 
      TimerCallback timerCallback = c => 
      { 
       var webClient = (WebClient) c; 
       if (!webClient.IsBusy) return; 
       webClient.CancelAsync(); 
       Debug.WriteLine(string.Format("DownloadFileTaskAsync (time out due): {0}", remotePath)); 
      }; 
      using (var timer = new Timer(timerCallback, client, timeOut, Timeout.Infinite)) 
      { 
       await client.DownloadFileTaskAsync(remotePath, localPath); 
      } 
      Debug.WriteLine(string.Format("DownloadFileTaskAsync (downloaded): {0}", remotePath)); 
      return new Tuple<string, string, Exception>(remotePath, localPath, null); 
     } 
    } 
    catch (Exception ex) 
    { 
     return new Tuple<string, string, Exception>(remotePath, null, ex); 
    } 
} 

public static class Extensions 
{ 
    public static Task ForEachAsync<TSource, TResult>(
     this IEnumerable<TSource> source, 
     Func<TSource, Task<TResult>> taskSelector, Action<TSource, TResult> resultProcessor) 
    { 
     var oneAtATime = new SemaphoreSlim(5, 10); 
     return Task.WhenAll(
      from item in source 
      select ProcessAsync(item, taskSelector, resultProcessor, oneAtATime)); 
    } 

    private static async Task ProcessAsync<TSource, TResult>(
     TSource item, 
     Func<TSource, Task<TResult>> taskSelector, Action<TSource, TResult> resultProcessor, 
     SemaphoreSlim oneAtATime) 
    { 
     TResult result = await taskSelector(item); 
     await oneAtATime.WaitAsync(); 
     try 
     { 
      resultProcessor(item, result); 
     } 
     finally 
     { 
      oneAtATime.Release(); 
     } 
    } 
} 

ich nicht die Unterschrift des ForEachAsync geändert haben den Grad der Parallelität zu wählen, ich lasse Sie es anpassen, wie Sie möchten.

Ausgabebeispiel:

DownloadFileTaskAsync (null local path): generating a temporary file name for http://cache.thephoenix.com/secure/uploadedImages/The_Phoenix/Music/CD_Review/main_OTR_Britney480.jpg 
DownloadFileTaskAsync (null local path): generating a temporary file name for http://ssimg.soundspike.com/artists/britneyspears_femmefatale_cd.jpg 
DownloadFileTaskAsync (null local path): generating a temporary file name for http://a323.yahoofs.com/ymg/albumreviewsuk__1/albumreviewsuk-526650850-1301400550.jpg?ymm_1xEDE5bu0tMi 
DownloadFileTaskAsync (null remote path): skipping 
DownloadFileTaskAsync (time out due): http://hangout.altsounds.com/geek/gars/images/3/9/8/5/2375.jpg 
DownloadFileTaskAsync (time out due): http://www.beat.com.au/sites/default/files/imagecache/630_315sr/images/article/header/2011/april/britney-spears-femme-fatale.jpg 
DownloadFileTaskAsync (time out due): http://cache.thephoenix.com/secure/uploadedImages/The_Phoenix/Music/CD_Review/main_OTR_Britney480.jpg 
DownloadFileTaskAsync (downloaded): http://newblog.thecmuwebsite.com/wp-content/uploads/2009/12/britneyspears1.jpg 
DownloadFileTaskAsync (downloaded): http://newblog.thecmuwebsite.com/wp-content/uploads/2009/12/britneyspears1.jpg 
DownloadFileTaskAsync (downloaded): http://static.guim.co.uk/sys-images/Music/Pix/site_furniture/2011/3/22/1300816812640/Femme-Fatale.jpg 
DownloadFileTaskAsync (downloaded): http://www.sputnikmusic.com/images/albums/72328.jpg 

Was nun auf 1 Minute kaum nimmt für das gleiche Ergebnis 10 Sekunden :)

Und großen Dank an den Autor dieser 2 Beiträge verwendet aufzunehmen:

http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx

http://blogs.msdn.com/b/pfxteam/archive/2012/03/04/10277325.aspx

+1

Große Erweiterungsmethoden! – nullable

+0

Ja, sie sind sehr hilfreich, danke! – Aybe

+1

Guter job sir :) –

Verwandte Themen