2013-10-04 4 views
8

parallèle EDITfichiers de manière asynchrone et téléchargement

J'ai changé le titre de la question afin de refléter la question que j'avais, mais aussi une réponse sur la façon d'y parvenir facilement.


Je suis en train de faire la 2ème méthode pour revenir Task<TResult> au lieu de Task comme en 1ère méthode, mais je reçois une cascade d'erreurs en raison d'essayer de le réparer.

  • I ajouté return avant await body(partition.Current);
  • À son tour, il me demande d'ajouter une déclaration de retour ci-dessous alors j'ai ajouté return null ci-dessous
  • Mais maintenant, l'instruction select se plaint qu'il ne peut pas en déduire l'argument du type de la requête
  • Je change Task.Run en Task.Run<TResult> mais sans succès.

Comment puis-je résoudre ce problème?

La première méthode vient de http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx, la deuxième méthode est la surcharge que j'essaie de créer.

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); 
      })); 
    } 
} 

Exemple d'utilisation:

Avec cette méthode, je voudrais télécharger plusieurs fichiers en parallèle et de manière asynchrone:

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

Ce que vous attendez du résultat n'est pas clair du tout. Vous passez toute une séquence de valeurs 'T', et exécutez la même fonction sur les deux - quel résultat attendriez-vous de sortir de la' tâche 'retourné? –

+0

Je voudrais obtenir une tâche dans ce cas, j'ai ajouté un exemple sur ma question. – Aybe

+0

* "Avec cette méthode, je voudrais télécharger plusieurs fichiers en parallèle et de manière asynchrone" *: 'Parallel.Foreach' ne suffit pas? –

Répondre

23

Je l'ai résolu et le poster ici, pourrait aider toute personne ayant la même problème.

Mon besoin initial était un petit assistant qui permettrait de télécharger rapidement des images mais aussi de simplement abandonner la connexion si le serveur ne répond pas rapidement, tout cela en parallèle et de manière asynchrone.

Cette aide vous renverra un tuple contenant le chemin distant, le chemin local et l'exception si une erreur s'est produite; donc très utile car il est toujours bon de savoir pourquoi les téléchargements défectueux ont failli. Je pense que j'ai oublié aucune des situations qui peuvent survenir lors d'un téléchargement, mais vous pouvez le commenter.

  • Vous spécifiez une liste des urls télécharger
  • Vous pouvez spécifier un nom de fichier local où il sera sauvé, sinon on sera généré pour vous
  • En option une durée pour l'annulation d'un téléchargement (à portée de main pour les serveurs lents ou non joignables)

Vous pouvez simplement utiliser DownloadFileTaskAsync lui-même ou utiliser l'aide ForEachAsync pour les téléchargements parallèles et asynchrones.

Code avec un exemple sur la façon de l'utiliser:

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(); 
     } 
    } 
} 

Je n'ai pas changé la signature de ForEachAsync de choisir le niveau de parallélisme, je vais vous laisser régler comme vous le souhaitez.

Exemple de sortie:

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 

Ce qui permet de prendre jusqu'à 1 minute maintenant prend à peine 10 secondes pour le même résultat :)

Et un grand merci à l'auteur de ces 2 messages:

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

De bonnes méthodes d'extension! – nullable

+0

Oui, ils sont très utiles, merci! – Aybe

+1

Bon travail monsieur :) –

Questions connexes