C# 同时下载尽可能多的文件

C# 同时下载尽可能多的文件,c#,asynchronous,parallel-processing,C#,Asynchronous,Parallel Processing,我想从随机生成的URI下载图像,我发现最快的方式是不可阻挡的 我正在使用预生成的列表的URI,达到大约400imgs/分钟(大约是使用标准线程时的8倍),但我希望它能够持续生成URI并下载新图像,直到我说暂停为止。如何做到这一点 private void StartButton_Click(object sender, EventArgs e) { List<string> ImageURI; GenerateURIs(out ImageURI); // create

我想从随机生成的URI下载图像,我发现最快的方式是不可阻挡的

我正在使用预生成的
列表
的URI,达到大约400imgs/分钟(大约是使用标准线程时的8倍),但我希望它能够持续生成URI并下载新图像,直到我说暂停为止。如何做到这一点

private void StartButton_Click(object sender, EventArgs e)
{
    List<string> ImageURI;
    GenerateURIs(out ImageURI); // creates list of 1000 uris
    ImageNames.AsParallel().WithDegreeOfParallelism(50).Sum(s => DownloadFile(s));
}

private int DownloadFile(string URI)
{
    try
    {
        HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(URI);
        webrequest.Timeout = 10000;
        webrequest.ReadWriteTimeout = 10000;
        webrequest.Proxy = null;
        webrequest.KeepAlive = false;
        HttpWebResponse webresponse = (HttpWebResponse)webrequest.GetResponse();

        using (Stream sr = webrequest.GetResponse().GetResponseStream())
        {
            DownloadedImages++;
            using (MemoryStream ms = new MemoryStream())
            {
                sr.CopyTo(ms);
                byte[] ImageBytes = ms.ToArray();
                if (ImageBytes.Length == 503)
                {
                    InvalidImages++;
                    return 0;
                }
                else
                {
                    ValidImages++;
                    using (var Writer = new BinaryWriter(new FileStream("images/" + (++FilesIndex).ToString() + ".png", FileMode.Append, FileAccess.Write)))
                    {
                        Writer.Write(ImageBytes);
                    }
                }
            }
        }          
    }
    catch (Exception e)
    {
        return 0;
    }            

    return 0;
}
private void StartButton_单击(对象发送方,事件参数e)
{
列出ImageURI;
GenerateURIs(out-ImageURI);//创建1000个uri的列表
ImageNames.AsParallel().WithDegreeOfParallelism(50).Sum(s=>DownloadFile(s));
}
私有int下载文件(字符串URI)
{
尝试
{
HttpWebRequest webrequest=(HttpWebRequest)webrequest.Create(URI);
webrequest.Timeout=10000;
webrequest.ReadWriteTimeout=10000;
webrequest.Proxy=null;
webrequest.KeepAlive=false;
HttpWebResponse webresponse=(HttpWebResponse)webrequest.GetResponse();
使用(Stream sr=webrequest.GetResponse().GetResponseStream())
{
下载图像++;
使用(MemoryStream ms=new MemoryStream())
{
高级文员(ms);
byte[]ImageBytes=ms.ToArray();
如果(ImageBytes.Length==503)
{
无效图像++;
返回0;
}
其他的
{
ValidImages++;
使用(var Writer=new BinaryWriter(新文件流(“images/”+(++fileindex.ToString()+“.png”、FileMode.Append、FileAccess.Write)))
{
Writer.Write(ImageBytes);
}
}
}
}          
}
捕获(例外e)
{
返回0;
}            
返回0;
}

您正在寻找的是一个生产者/消费者模型,在该模型中,生产者将项目添加到队列中,消费者从队列中提取项目
BlockingCollection
使这非常容易。创建一个
BlockingCollection
,让您的制作人在您生成项目时继续向其中添加项目,完成后调用
completedadding
,并让您的消费者使用
GetConsumingEnumerable
,您可以调用该可枚举项上的确切代码

您需要将生产者代码和消费代码都移动到非UI线程中,这样它们就不会阻塞UI,并且可以并行地生成/消费数据


还要注意,当前在
DownloadFile
方法中,您正在修改和访问实例数据,尽管这个方法可能会从不同的线程并发调用。像递增索引这样的操作是不安全的,因为它不是一个原子操作,这会导致代码产生可能的副作用。您需要避免在这些不同的线程之间使用共享状态,或者正确地同步对该共享状态的访问。

首先,您当前的代码不是线程安全的<代码>无效图像,
下载图像
有效图像
都需要同步

也就是说,使用异步而不是线程可以更有效地完成这项工作。由于本例中几乎所有的“工作”都是IO绑定的,因此异步可能是一种更好、更具可扩展性的方法

请尝试以下方法:

private async void StartButton_Click(object sender, EventArgs e)
{
    List<string> ImageURI;
    GenerateURIs(out ImageURI); // creates list of 1000 uris

    var requests = ImageURI
        .Select(uri => (new WebClient()).DownloadDataTaskAsync(uri))
        .Select(SaveImageFile);

    await Task.WhenAll(requests);
}

private Task SaveImageFile(Task<byte[]> data)
{
    try
    {
        byte[] ImageBytes = await data;
        DownloadedImages++;

        if (ImageBytes.Length == 503)
        {
            InvalidImages++;
            return;
        }

        ValidImages++;
        using (var file = new FileStream("images/" + (++FilesIndex).ToString() + ".png", FileMode.Append, FileAccess.Write))
        {
            await Writer.WriteAsync(ImageBytes, 0, ImageBytes.Length);
        }
    }
    catch (Exception e)
    {
    }            

    return;
}
private async void StartButton_单击(对象发送方,事件参数e)
{
列出ImageURI;
GenerateURIs(out-ImageURI);//创建1000个uri的列表
var请求=ImageURI
.Select(uri=>(新WebClient()).DownloadDataTaskAsync(uri))
.选择(保存图像文件);
等待任务。何时(请求);
}
专用任务SaveImageFile(任务数据)
{
尝试
{
byte[]ImageBytes=等待数据;
下载图像++;
如果(ImageBytes.Length==503)
{
无效图像++;
返回;
}
ValidImages++;
使用(var file=newfilestream(“images/”+(++fileindex.ToString()+“.png”、FileMode.Append、FileAccess.Write))
{
wait Writer.WriteAsync(ImageBytes,0,ImageBytes.Length);
}
}
捕获(例外e)
{
}            
返回;
}
请注意,使用async/await,您不再需要担心同步问题,因为这些值仍将在主UI线程上设置


至于暂停,有多种选择-您可以添加一个是否继续执行数据的标志,或者使用
CancellationTokenSource
在整个操作过程中提供取消支持。

除了GUI阻塞之外,您还有其他问题吗,因为如果你只想知道如何在不阻塞UI的情况下完成一些工作,那么有大量的资源可以向你展示如何做到这一点。如果你有任何其他问题,你需要解释它们是什么。我的意思是别的,我已经发了帖子。我希望能够停止整个下载过程,并运行它更动态,而不是从预定义的列表。(正如我在第一句话后提到的)这看起来很聪明,我稍后会尝试一下,但是如何让它持续下载,而不是从预定义列表中下载?@user2843974只是这些方法的输入是如何生成的。如果不知道URI是如何创建的,就不可能回答这个问题……这只是站点名称+后面的随机字符串slash@user2843974你只需做一个循环,下载一些图片(里面有wait),然后用一个取消令牌停止。因为它使用异步,所以它永远不会阻塞…它对我来说工作得非常完美。我以前尝试过异步,但我无法做到这一点,因为我仍然不能真正理解异步。非常感谢。