我想从随机生成的URI中下载图像,我找到的最快的方法是不可阻挡的。
我正在使用预生成的List<string>
URI达到约400个/分钟(比使用标准线程时多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;
}
答案 0 :(得分:1)
您正在寻找的是生产者/消费者模型,在该模型中,您有一个生产者将项目添加到队列中,而消费者将项目从中拉出。 BlockingCollection
让这很容易。创建BlockingCollection
,让生产者在生成项目时继续向其添加项目,完成后调用CompleteAdding
,让消费者使用GetConsumingEnumerable
,您可以将其称为确切的可枚举的代码。
您需要将生产者代码和消费代码都移动到非UI线程中,这样它们都不会阻止UI并且可以并行生成/使用数据。
另请注意,目前在DownloadFile
方法中,您正在变异并访问实例数据,尽管可能会同时从不同的线程调用此方法。执行诸如递增索引之类的操作并不安全,因为它不是原子操作,这会导致代码产生可能的副作用。您需要避免在这些不同的线程之间使用共享状态,或者正确地同步对该共享状态的访问。
答案 1 :(得分:1)
首先,您当前的代码不是线程安全的。 InvalidImages
,DownloadedImages
和ValidImages
都需要同步。
话虽这么说,你可以使用异步而不是线程更有效地做到这一点。因为几乎所有的&#34;工作&#34;在这种情况下是IO绑定,async可能是一个更好,更可扩展的方法。
请改为尝试:
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;
}
请注意,使用async / await,您不再需要担心同步,因为这些值仍将在主UI线程上设置。
至于暂停,有各种选项 - 您可以添加是否继续执行数据的标志,或使用CancellationTokenSource
在整个操作中提供取消支持。