【问题标题】:Downloading 1,000+ files fast?快速下载 1,000 多个文件?
【发布时间】:2018-04-23 02:00:14
【问题描述】:

我目前有一个链接到图像或 MP4 文件的 URL 列表,重新编码以更快地运行的最佳方法是什么?我知道我可以运行多个线程甚至并行运行,但最好的方法是什么?

我不太担心速度,只要它没有现在那么慢,但我不想压倒设备的资源,例如试图加快速度的 CPU。

public static void Download(string saveLocation, List<string> urls)
{
    using (var client = new WebClient())
    {
        foreach (var url in urls)
        {
            Console.WriteLine("Downloading: " + url);
            client.DownloadFile(url, saveLocation + "/" + url.Split('/').Last());
        }
    }
}

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    更新

    Jimi 刚刚在评论中向我指出,DownloadFileAsync 是一个事件驱动的调用,不能等待。虽然有一个 WebClient.DownloadFileTaskAsync 版本,这将是本示例中使用的合适版本,但它是一个等待调用并返回一个 Task

    以异步方式将指定资源下载到本地文件 使用任务对象进行操作。

    原答案

    我知道我可以运行多个线程,甚至可以并行运行,但是什么是 最好的方法

    是的,您可以使其并行化并控制您使用的资源。

    我不太担心速度,只要它不像正确的那么慢 现在,但我不想压倒设备的资源,例如 CPU 试图加快速度

    您应该能够实现这一点并很好地配置它。


    好的,所以有很多方法可以做到这一点。以下是一些需要考虑的事情:

    • 您有 1000 个 IO bound 任务(而不是 CPU bound 任务)
    • 有了这么多文件,您需要某种并行性并能够配置并发任务的数量。
    • 您将希望以 async / await 模式执行此操作,这样您就不会在 IO 完成端口上浪费系统资源或破坏 CPU

    一些直接的解决方案:

    • Tasks 和 asnyc / await 模式中的 WaitAll,这是一个很好的方法,但是限制并发任务有点棘手。
    • 您有 Parallel.ForEach 和 Parallel.For,这是限制并发工作负载的好方法,但它不适合 IO 绑定任务
    • 或者您可能会考虑的另一个选择是 Microsoft Dataflow (Task Parallel Library),我最近越来越喜欢这些库,因为它们可以为您提供两全其美的东西。

    请注意:还有很多其他方法。

    所以Parallel.ForEach 使用线程池。此外,IO 绑定 操作会阻塞那些等待设备响应的线程并占用资源。这里的一般经验法则是

    • 如果您有CPU 绑定代码,Parallel.ForEach 是合适的;
    • 虽然如果你有 IO 绑定 代码,Asynchrony 是合适的。

    这种情况下,下载一个文件明明是I/O,有DownloadFileAsync版本,1000个文件要下载,所以你最好用async/await模式和对并发任务的某种类型的限制


    这是一个非常基本的示例,说明如何实现这一目标:

    给定

    public class WorkLoad
    {
        public string Url {get;set;}
        public string FileName {get;set;}
    
    }
    

    数据流示例

    public async Task DoWorkLoads(List<WorkLoad> workloads)
    {
       var options = new ExecutionDataflowBlockOptions
                         {
                            // add pepper and salt to taste
                            MaxDegreeOfParallelism = 50
                         };
    
       // create an action block
       var block = new ActionBlock<WorkLoad>(MyMethodAsync, options);
    
       // Queue them up
       foreach (var workLoad in workloads)
          block.Post(workLoad );
    
       // wait for them to finish
       block.Complete();
       await block.Completion;
    
    }
    
    ...
    
    // Notice we are using the async / await pattern
    public async Task MyMethodAsync(WorkLoad workLoad)
    {
       
        try
        {
            Console.WriteLine("Downloading: " + workLoad.Url);
            await client.DownloadFileAsync(workLoad.Url, workLoad.FileName);
        }
        catch (Exception)
        {
            // probably best to add some error checking some how
        }
    }
    

    总结

    这种方法给你异步,也给你MaxDegreeOfParallelism,不浪费资源,让IO成为IO

    免责声明,DataFlow 可能不是您想要的,但我只是想给您更多信息

    免责声明 2,另外上面的代码还没有经过测试,我会认真考虑先研究这项技术,并彻底做你的尽职调查。


    Loosely related demo here

    【讨论】:

    • 我在这里看到了一个问题,因为DownloadFileAsync 是不可等待的,它是事件驱动的。这会从方法声明中退出异步,ActionBlock 会抱怨。
    • @jimi 谢谢和好收获,这让我的整个演讲无效。很快就会修改它
    • 我不确定我是如何错过这个答案的。我刚刚尝试过,它只是一个小问题,client 没有被声明,尽管我希望你只是为此新建一个 Web 客户端。除此之外,很好的回答谢谢。
    • 刚刚注意到 II 在尝试等待 DownloadFileAsync 作为它的无效返回类型时似乎遇到了错误。
    • @user692959239 你需要使用 DownloadFileTaskAsync
    【解决方案2】:

    将您的函数标记为async 并使用DownloadFileAsync 保存文件。将任务列表捕获到一个新列表中,然后在返回之前使用 Task.WhenAll(yourTasks) 将该列表 await。

    【讨论】:

    • 一个代码示例真的很有帮助,我不知道为什么我需要一个任务列表以及它是如何工作的。
    • 显然每次下载(500+)都做一个任务是不好的,那么我将如何对下载进行分区呢?
    【解决方案3】:

    您可以使用 Parallel.ForEach 并行下载文件。我过去曾使用过类似的东西。

        using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileDownloader
    {
        class Program
        {
            static void Main(string[] args)
            {
                List allUrls = GetUrls().Select(x=>x.Trim()).ToList();
    
                Parallel.ForEach(allUrls, new ParallelOptions() { MaxDegreeOfParallelism = 10 }, url =>
                {
                    try
                    {
                        WebRequest request = WebRequest.Create(url);
                        WebResponse response = request.GetResponse();
                        string originalFileName = response.ResponseUri.AbsolutePath.Substring(response.ResponseUri.AbsolutePath.LastIndexOf("/") + 1);
                        Stream streamWithFileBody = response.GetResponseStream();
                        using (Stream output = File.OpenWrite(@"C:\Ebooks_New\" + originalFileName))
                        {
                            streamWithFileBody.CopyTo(output);
                        }
    
                        Console.WriteLine("Downloded : " + originalFileName);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Unable to Download : " + ex.ToString());
    
                    }
                });
    
                Console.WriteLine("Finished : ************************");
                Console.ReadKey();
            }
    
            public static List GetUrls()
            {
                return new List() // Put list of URLs here
                {
                    "http://ligman.me/1IW1oab  ",
        "http://ligman.me/1Uixtlq  ",
        "http://ligman.me/1R9Ubgt  ",
        "http://ligman.me/1H4VXHT  ",
        "http://ligman.me/1f8XUKy  ",
        "http://ligman.me/1HBEUPi  ",
        "http://ligman.me/1NDTZR4  ",
        "http://ligman.me/1Uiy2f9  ",
        "http://ligman.me/1epZ0QU  ",
        "http://ligman.me/1JIhgjA  ",
        "http://ligman.me/1CQX5uG  ",
       }
      }
     }
    }
    

    【讨论】:

    • 这不会在双处理器机器上运行得更快。我也得到一个错误:Unable to Download : System.Net.WebException: An exception occurred during a WebClient request. ---&gt; System.IO.IOException: The process cannot access the file 'c:\users\admin\desktop\scraper\scraper\bin\Debug\downloads\username123\nz709zOcFR1qlk60o_480.mp4' because it is being used by another process. 下载 500 个文件时两次。
    猜你喜欢
    • 2012-12-10
    • 1970-01-01
    • 2015-03-28
    • 1970-01-01
    • 2022-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多