WebClient DownloadData 方法冻结表单
WebClient DownloadData method freezes Form
我正在使用 WebClient 对象的 DownloadData 从几个网站下载网站图标。
我正在接收字节数组的响应,一切正常,除了一件事:当 DownloadData 方法被执行时,它'将冻结我的表单,直到方法 returns.
现在,我已经通过使用 BackgroundWorker 对象 解决了这个问题,但我很好奇如何使用 实现同样的事情System.Threading.Thread.
我尝试创建另一个线程来下载网站图标,然后循环我的 mainThread 直到线程完成处理,然后使用 Abort() 方法中止线程,但到目前为止我的表单在执行期间被冻结另一个线程。
这是我用来创建另一个线程的代码:
bool downloadFavIcon_Completed = false;
private void downloadFavIcon()
{
downloadFavIcon_Completed = false;
Byte[] dl;
System.IO.MemoryStream dlMem;
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
Bitmap dlImg;
String[] addr = new String[24];
addr[0] = @"http://google.com/favicon.ico";
addr[1] = @"http://microsoft.com/favicon.ico";
addr[2] = @"http://freesfx.com/favicon.ico";
addr[3] = @"http://yahoo.com/favicon.ico";
addr[4] = @"http://downloadha.com/favicon.ico";
addr[5] = @"http://hp.com/favicon.ico";
addr[6] = @"http://bing.com/favicon.ico";
addr[7] = @"http://webassign.com/favicon.ico";
addr[8] = @"http://youtube.com/favicon.ico";
addr[9] = @"https://twitter.com/favicon.ico";
addr[10] = @"http://cc.com/favicon.ico";
addr[11] = @"http://whosebug.com/favicon.ico";
addr[12] = @"http://vb6.us/favicon.ico";
addr[13] = @"http://facebook.com/favicon.ico";
addr[14] = @"http://flickr.com/favicon.ico";
addr[15] = @"http://linkedin.com/favicon.ico";
addr[16] = @"http://blogger.com/favicon.ico";
addr[17] = @"http://blogfa.com/favicon.ico";
addr[18] = @"http://metal-archives.com/favicon.ico";
addr[19] = @"http://wordpress.com/favicon.ico";
addr[20] = @"http://metallica.com/favicon.ico";
addr[21] = @"http://wikipedia.org/favicon.ico";
addr[22] = @"http://visualstudio.com/favicon.ico";
addr[23] = @"http://evernote.com/favicon.ico";
for (int i = 0; i < addr.Length; i++)
{
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
dl = client.DownloadData(addr[i]);
dlMem = new System.IO.MemoryStream(dl);
dlImg = new Bitmap(dlMem);
}
catch (Exception)
{
dlImg = new Bitmap(Properties.Resources.defaultFavIcon);
}
}
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
passAddDisplay.Image = favCollection;
downloadFavIcon_Completed = true;
}
private void button2_Click(object sender, EventArgs e)
{
Thread downloader = new Thread(new ThreadStart(downloadFavIcon));
downloader.Start();
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
downloader.Abort();
}
注意:passAddDisplay 是一个已经放在我的表单上的图片框。
如何改进我的应用程序以避免在 WebClient.DownloadData 执行期间被冻结? (我不想使用 Application.DoEvents())
是的,它是一种同步方法,它会导致线程等待而不处理消息,直到它returns;你应该试试它的异步版本,它没有。
欢迎来到 Stack Overflow...
通过检查您的循环,很明显您正在锁定循环中的 UI 线程。
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
是您的 UI 锁定的原因。理想情况下,这将是后台工作人员的工作,因为它被设计为在后台 运行 并提供事件以返回到您的 UI 线程。这可以使用线程来编写,但是应该使用后台工作者。
现在,如果您真的想使用 Thread
对象来编写此代码,那么我建议您为下载器创建一个 class 并创建事件。因此我们可以编写简单的事件,例如
- 已完成活动
- 已取消的活动
- UI 进度事件
我不推荐这种方法(往下读)
只需从创建一个新的 class Downloader
开始,这是一个示例(最低限度)
public class Downloader
{
/// <summary>
/// Delegate Event Handler for the downloading progress
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void DownloaderProgressEventHandler(Downloader sender, DownloaderProgressEventArgs e);
/// <summary>
/// Delegate Event Handler for the completed event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void DownloaderCompletedEventHandler(Downloader sender, DownloaderCompletedEventArgs e);
/// <summary>
/// The completed event
/// </summary>
public event DownloaderCompletedEventHandler Completed;
/// <summary>
/// The cancelled event
/// </summary>
public event EventHandler Cancelled;
/// <summary>
/// the progress event
/// </summary>
public event DownloaderProgressEventHandler Progress;
/// <summary>
/// the running thread
/// </summary>
Thread thread;
/// <summary>
/// the aborting flag
/// </summary>
bool aborting = false;
//the addresses
String[] addr = new String[] {
"http://google.com/favicon.ico",
"http://microsoft.com/favicon.ico",
"http://freesfx.com/favicon.ico",
"http://yahoo.com/favicon.ico",
"http://downloadha.com/favicon.ico",
"http://hp.com/favicon.ico",
"http://bing.com/favicon.ico",
"http://webassign.com/favicon.ico",
"http://youtube.com/favicon.ico",
"https://twitter.com/favicon.ico",
"http://cc.com/favicon.ico",
"http://whosebug.com/favicon.ico",
"http://vb6.us/favicon.ico",
"http://facebook.com/favicon.ico",
"http://flickr.com/favicon.ico",
"http://linkedin.com/favicon.ico",
"http://blogger.com/favicon.ico",
"http://blogfa.com/favicon.ico",
"http://metal-archives.com/favicon.ico",
"http://wordpress.com/favicon.ico",
"http://metallica.com/favicon.ico",
"http://wikipedia.org/favicon.ico",
"http://visualstudio.com/favicon.ico",
"http://evernote.com/favicon.ico"
};
/// <summary>
/// Starts the downloader
/// </summary>
public void Start()
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.aborting = false;
this.thread = new Thread(new ThreadStart(runDownloader));
this.thread.Start();
}
/// <summary>
/// Starts the downloader
/// </summary>
/// <param name="addresses"></param>
public void Start(string[] addresses)
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.addr = addresses;
this.Start();
}
/// <summary>
/// Aborts the downloader
/// </summary>
public void Abort()
{
if (this.aborting)
return;
this.aborting = true;
this.thread.Join();
this.thread = null;
this.aborting = false;
if (this.Cancelled != null)
this.Cancelled(this, EventArgs.Empty);
}
/// <summary>
/// runs the downloader
/// </summary>
void runDownloader()
{
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
for (var i = 0; i < this.addr.Length; i++)
{
if (aborting)
break;
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
byte[] dl = client.DownloadData(addr[i]);
using (var stream = new MemoryStream(dl))
{
using (var dlImg = new Bitmap(stream))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
catch (Exception)
{
using (var dlImg = new Bitmap(Properties.Resources.defaultFacIcon))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
if (aborting)
break;
if (this.Progress != null)
this.Progress(this, new DownloaderProgressEventArgs
{
Completed = i + 1,
Total = this.addr.Length
});
}
if (!aborting && this.Completed != null)
{
this.Completed(this, new DownloaderCompletedEventArgs
{
Bitmap = favCollection
});
}
this.thread = null;
}
/// <summary>
/// Downloader progress event args
/// </summary>
public class DownloaderProgressEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the completed images
/// </summary>
public int Completed { get; set; }
/// <summary>
/// Gets or sets the total images
/// </summary>
public int Total { get; set; }
}
/// <summary>
/// Downloader completed event args
/// </summary>
public class DownloaderCompletedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the bitmap
/// </summary>
public Bitmap Bitmap { get; set; }
}
}
现在已经分配了代码,但让我们快速查看一下。首先,我们为 Completed 和 Progress 事件定义了 2 个代表。这些代表接受下载器实例作为发送者和底部列出的特殊事件参数 classes。接下来是我们的 3 个事件(如上所列),这些事件将用于向下载器发出更改信号。
接下来我们定义字段。
Thread thread;
这是对调用“Start()”方法时将创建的线程的引用。
bool aborting = false;
这是一个简单的标志,用于向线程发出我们应该中止的信号。现在我决定使用一个标志并让线程优雅地完成,而不是调用 Thread.Abort()
方法。这确保所有清理工作都能正常进行。
string[] addres =....
我们的初始地址。
到目前为止,这一切都很简单。接下来是我们的 Start()
方法。我提供了两种不同的方法。其中一种方法接受新的 string[]
地址下载(不那么重要)。
你会在这个方法中注意到
/// <summary>
/// Starts the downloader
/// </summary>
public void Start()
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.aborting = false;
this.thread = new Thread(new ThreadStart(runDownloader));
this.thread.Start();
}
我们做的第一件事是检查是否设置了中止标志。如果是,则忽略开始调用(您可能会抛出异常)。接下来我们检查线程是否不为空。如果线程不为空,那么我们的下载器是 运行ning。最后,我们只需将我们的中止标志重置为 false
并开始我们新的 Thread
.
向下移动到 Abort()
方法。此方法将首先检查是否设置了 aborting
标志。如果是这样,那么什么都不做。接下来我们将 aborting
标志设置为 true。下一步,我会警告你这个 WILL 阻止你的调用线程是调用方法 Thread.Join()
,它将线程加入我们的调用线程。基本上等待线程退出。
最后我们简单地将线程实例设置为 null,将 aborting
标志重置为 false 并触发 Cancelled
事件(如果已订阅)。
接下来是执行下载的主要方法。首先你会注意到我移动了你的变量并对一次性对象使用了 using
语句。 (那是另外一个话题了)
runDownloader()
方法的重要部分是它定期检查 'aborting' 标志。如果此标志曾经设置为 true
,则 downloader
会停在那里。现在请注意,在 WebClient
下载图像时,您可能会遇到这样一种情况,即调用中止。理想情况下,您会让 WebClient
完成请求,正确处理然后退出循环。
每次下载图片后都会触发进度事件(如果已订阅)。最后,当迭代完成并且所有图像都已下载时,"Completed" 事件随编译的位图图像一起触发。
暂停呼吸
现在这一切都很棒..但是你如何使用它。很简单,我创建了一个带有按钮、进度条和图片框的表单。该按钮将用于启动和停止下载器,进度条用于处理进度事件和完成图像的图片框。
这是一个示例程序,我已经对其中的部分内容进行了评论。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.progressBar1.Visible = false;
this.progressBar1.Enabled = false;
}
Downloader downloader;
/// <summary>
/// starts \ stop button pressed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
//if downloader is not null then abort it
if (downloader != null)
{
downloader.Abort();
return;
}
//setup and start the downloader
this.progressBar1.Value = 0;
this.progressBar1.Minimum = 0;
this.progressBar1.Enabled = true;
this.progressBar1.Visible = true;
this.downloader = new Downloader();
this.downloader.Progress += downloader_Progress;
this.downloader.Completed += downloader_Completed;
this.downloader.Cancelled += downloader_Cancelled;
this.downloader.Start();
}
/// <summary>
/// downloader cancelled event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Cancelled(object sender, EventArgs e)
{
this.unhookDownloader();
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
MessageBox.Show(this, "Cancelled");
});
else
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
MessageBox.Show(this, "Cancelled");
}
}
/// <summary>
/// downloader completed event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Completed(Downloader sender, Downloader.DownloaderCompletedEventArgs e)
{
this.unhookDownloader();
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
this.pictureBox1.Image = e.Bitmap;
MessageBox.Show(this, "Completed");
});
else
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
this.pictureBox1.Image = e.Bitmap;
MessageBox.Show(this, "Completed");
}
}
/// <summary>
/// downloader progress event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Progress(Downloader sender, Downloader.DownloaderProgressEventArgs e)
{
if (this.progressBar1.InvokeRequired)
this.progressBar1.Invoke((MethodInvoker)delegate
{
this.progressBar1.Value = e.Completed;
this.progressBar1.Maximum = e.Total;
});
else
{
this.progressBar1.Value = e.Completed;
this.progressBar1.Maximum = e.Total;
}
}
/// <summary>
/// unhooks the events handlers and sets the downloader to null
/// </summary>
void unhookDownloader()
{
this.downloader.Progress -= downloader_Progress;
this.downloader.Completed -= downloader_Completed;
this.downloader.Cancelled -= downloader_Cancelled;
this.downloader = null;
}
}
这是如何使用 Thread
对象完成工作的简单实现。在我看来,这是太多的工作。现在让我们在 Background Worker 中完成它。
我强烈推荐这种方法
为什么你会说?后台工作者为我们提供了我们试图实现的 tested 和 supported 方法(以及更多)。您会注意到下载图像和检测取消的实际工作是相对相同的。但是您会注意到,在发布已完成和进度事件时,我并不(那么)担心跨线程问题。
private void button2_Click(object sender, EventArgs e)
{
if (this.worker != null && this.worker.IsBusy)
{
this.worker.CancelAsync();
return;
}
string[] addr = new string[] {".... our full addresss lists" };
this.progressBar1.Maximum = addr.Length;
this.progressBar1.Value = 0;
this.progressBar1.Visible = true;
this.progressBar1.Enabled = true;
this.worker = new BackgroundWorker();
this.worker.WorkerSupportsCancellation = true;
this.worker.WorkerReportsProgress = true;
this.worker.ProgressChanged += (s, args) =>
{
this.progressBar1.Value = args.ProgressPercentage;
};
this.worker.RunWorkerCompleted += (s, args) =>
{
this.progressBar1.Visible = false;
this.progressBar1.Enabled = false;
if (args.Cancelled)
{
MessageBox.Show(this, "Cancelled");
worker.Dispose();
worker = null;
return;
}
var img = args.Result as Bitmap;
if (img == null)
{
worker.Dispose();
worker = null;
return;
}
this.pictureBox1.Image = img;
MessageBox.Show(this, "Completed");
worker.Dispose();
worker = null;
};
this.worker.DoWork += (s, args) =>
{
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
for (var i = 0; i < addr.Length; i++)
{
if (worker.CancellationPending)
break;
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
byte[] dl = client.DownloadData(addr[i]);
using (var stream = new MemoryStream(dl))
{
using (var dlImg = new Bitmap(stream))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
catch (Exception)
{
using (var dlImg = new Bitmap(Properties.Resources.defaultFacIcon))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
if (worker.CancellationPending)
break;
this.worker.ReportProgress(i);
}
if (worker.CancellationPending)
{
g.Dispose();
favCollection.Dispose();
args.Cancel = true;
return;
}
args.Cancel = false;
args.Result = favCollection;
};
worker.RunWorkerAsync();
}
我希望这有助于理解一些可能的方法来实现您想要实现的目标。
-妮可
我会考虑为此使用 Microsoft 的 Reactive Framework。它自动处理后台线程,并将非常有效地清理所有一次性引用。 NuGet "Rx-Main" & "Rx-WinForms"/"Rx-WPF".
首先,从您的地址数组开始:
var addr = new []
{
"http://google.com/favicon.ico",
// DELETED FOR BREVITY
"http://evernote.com/favicon.ico",
};
现在,定义一个查询以异步获取图像:
var query =
from a in addr.ToObservable().Select((url, i) => new { url, i })
from dl in Observable
.Using(
() => new System.Net.WebClient(),
wc => Observable.FromAsync(() => wc.DownloadDataTaskAsync(a.url)))
from bitmap in Observable
.Using(
() => new System.IO.MemoryStream(dl),
ms => Observable.Start(() => new Bitmap(ms)))
.Catch(ex => Observable.Return(new Bitmap(Properties.Resources.defaultFavIcon)))
select new { x = (a.i % 6) * 16, y = (a.i / 6) * 16, bitmap };
最后,等待所有图像都进来,然后在UI线程上,创建合成图像并将其分配给passAddDisplay
控件。
query
.ToArray()
.ObserveOn(passAddDisplay)
.Subscribe(images =>
{
var favCollection = new Bitmap(96, 64);
using(var g = Graphics.FromImage(favCollection))
{
foreach (var image in images)
{
g.DrawImage(image.bitmap, image.x, image.y, 16, 16);
image.bitmap.Dispose();
}
}
passAddDisplay.Image = favCollection;
});
我测试了查询,它工作正常。
我正在使用 WebClient 对象的 DownloadData 从几个网站下载网站图标。
我正在接收字节数组的响应,一切正常,除了一件事:当 DownloadData 方法被执行时,它'将冻结我的表单,直到方法 returns.
现在,我已经通过使用 BackgroundWorker 对象 解决了这个问题,但我很好奇如何使用 实现同样的事情System.Threading.Thread.
我尝试创建另一个线程来下载网站图标,然后循环我的 mainThread 直到线程完成处理,然后使用 Abort() 方法中止线程,但到目前为止我的表单在执行期间被冻结另一个线程。
这是我用来创建另一个线程的代码:
bool downloadFavIcon_Completed = false;
private void downloadFavIcon()
{
downloadFavIcon_Completed = false;
Byte[] dl;
System.IO.MemoryStream dlMem;
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
Bitmap dlImg;
String[] addr = new String[24];
addr[0] = @"http://google.com/favicon.ico";
addr[1] = @"http://microsoft.com/favicon.ico";
addr[2] = @"http://freesfx.com/favicon.ico";
addr[3] = @"http://yahoo.com/favicon.ico";
addr[4] = @"http://downloadha.com/favicon.ico";
addr[5] = @"http://hp.com/favicon.ico";
addr[6] = @"http://bing.com/favicon.ico";
addr[7] = @"http://webassign.com/favicon.ico";
addr[8] = @"http://youtube.com/favicon.ico";
addr[9] = @"https://twitter.com/favicon.ico";
addr[10] = @"http://cc.com/favicon.ico";
addr[11] = @"http://whosebug.com/favicon.ico";
addr[12] = @"http://vb6.us/favicon.ico";
addr[13] = @"http://facebook.com/favicon.ico";
addr[14] = @"http://flickr.com/favicon.ico";
addr[15] = @"http://linkedin.com/favicon.ico";
addr[16] = @"http://blogger.com/favicon.ico";
addr[17] = @"http://blogfa.com/favicon.ico";
addr[18] = @"http://metal-archives.com/favicon.ico";
addr[19] = @"http://wordpress.com/favicon.ico";
addr[20] = @"http://metallica.com/favicon.ico";
addr[21] = @"http://wikipedia.org/favicon.ico";
addr[22] = @"http://visualstudio.com/favicon.ico";
addr[23] = @"http://evernote.com/favicon.ico";
for (int i = 0; i < addr.Length; i++)
{
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
dl = client.DownloadData(addr[i]);
dlMem = new System.IO.MemoryStream(dl);
dlImg = new Bitmap(dlMem);
}
catch (Exception)
{
dlImg = new Bitmap(Properties.Resources.defaultFavIcon);
}
}
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
passAddDisplay.Image = favCollection;
downloadFavIcon_Completed = true;
}
private void button2_Click(object sender, EventArgs e)
{
Thread downloader = new Thread(new ThreadStart(downloadFavIcon));
downloader.Start();
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
downloader.Abort();
}
注意:passAddDisplay 是一个已经放在我的表单上的图片框。
如何改进我的应用程序以避免在 WebClient.DownloadData 执行期间被冻结? (我不想使用 Application.DoEvents())
是的,它是一种同步方法,它会导致线程等待而不处理消息,直到它returns;你应该试试它的异步版本,它没有。
欢迎来到 Stack Overflow...
通过检查您的循环,很明显您正在锁定循环中的 UI 线程。
while (!downloader.IsAlive) ;
while (!downloadFavIcon_Completed) ;
是您的 UI 锁定的原因。理想情况下,这将是后台工作人员的工作,因为它被设计为在后台 运行 并提供事件以返回到您的 UI 线程。这可以使用线程来编写,但是应该使用后台工作者。
现在,如果您真的想使用 Thread
对象来编写此代码,那么我建议您为下载器创建一个 class 并创建事件。因此我们可以编写简单的事件,例如
- 已完成活动
- 已取消的活动
- UI 进度事件
我不推荐这种方法(往下读)
只需从创建一个新的 class Downloader
开始,这是一个示例(最低限度)
public class Downloader
{
/// <summary>
/// Delegate Event Handler for the downloading progress
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void DownloaderProgressEventHandler(Downloader sender, DownloaderProgressEventArgs e);
/// <summary>
/// Delegate Event Handler for the completed event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void DownloaderCompletedEventHandler(Downloader sender, DownloaderCompletedEventArgs e);
/// <summary>
/// The completed event
/// </summary>
public event DownloaderCompletedEventHandler Completed;
/// <summary>
/// The cancelled event
/// </summary>
public event EventHandler Cancelled;
/// <summary>
/// the progress event
/// </summary>
public event DownloaderProgressEventHandler Progress;
/// <summary>
/// the running thread
/// </summary>
Thread thread;
/// <summary>
/// the aborting flag
/// </summary>
bool aborting = false;
//the addresses
String[] addr = new String[] {
"http://google.com/favicon.ico",
"http://microsoft.com/favicon.ico",
"http://freesfx.com/favicon.ico",
"http://yahoo.com/favicon.ico",
"http://downloadha.com/favicon.ico",
"http://hp.com/favicon.ico",
"http://bing.com/favicon.ico",
"http://webassign.com/favicon.ico",
"http://youtube.com/favicon.ico",
"https://twitter.com/favicon.ico",
"http://cc.com/favicon.ico",
"http://whosebug.com/favicon.ico",
"http://vb6.us/favicon.ico",
"http://facebook.com/favicon.ico",
"http://flickr.com/favicon.ico",
"http://linkedin.com/favicon.ico",
"http://blogger.com/favicon.ico",
"http://blogfa.com/favicon.ico",
"http://metal-archives.com/favicon.ico",
"http://wordpress.com/favicon.ico",
"http://metallica.com/favicon.ico",
"http://wikipedia.org/favicon.ico",
"http://visualstudio.com/favicon.ico",
"http://evernote.com/favicon.ico"
};
/// <summary>
/// Starts the downloader
/// </summary>
public void Start()
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.aborting = false;
this.thread = new Thread(new ThreadStart(runDownloader));
this.thread.Start();
}
/// <summary>
/// Starts the downloader
/// </summary>
/// <param name="addresses"></param>
public void Start(string[] addresses)
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.addr = addresses;
this.Start();
}
/// <summary>
/// Aborts the downloader
/// </summary>
public void Abort()
{
if (this.aborting)
return;
this.aborting = true;
this.thread.Join();
this.thread = null;
this.aborting = false;
if (this.Cancelled != null)
this.Cancelled(this, EventArgs.Empty);
}
/// <summary>
/// runs the downloader
/// </summary>
void runDownloader()
{
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
for (var i = 0; i < this.addr.Length; i++)
{
if (aborting)
break;
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
byte[] dl = client.DownloadData(addr[i]);
using (var stream = new MemoryStream(dl))
{
using (var dlImg = new Bitmap(stream))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
catch (Exception)
{
using (var dlImg = new Bitmap(Properties.Resources.defaultFacIcon))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
if (aborting)
break;
if (this.Progress != null)
this.Progress(this, new DownloaderProgressEventArgs
{
Completed = i + 1,
Total = this.addr.Length
});
}
if (!aborting && this.Completed != null)
{
this.Completed(this, new DownloaderCompletedEventArgs
{
Bitmap = favCollection
});
}
this.thread = null;
}
/// <summary>
/// Downloader progress event args
/// </summary>
public class DownloaderProgressEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the completed images
/// </summary>
public int Completed { get; set; }
/// <summary>
/// Gets or sets the total images
/// </summary>
public int Total { get; set; }
}
/// <summary>
/// Downloader completed event args
/// </summary>
public class DownloaderCompletedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the bitmap
/// </summary>
public Bitmap Bitmap { get; set; }
}
}
现在已经分配了代码,但让我们快速查看一下。首先,我们为 Completed 和 Progress 事件定义了 2 个代表。这些代表接受下载器实例作为发送者和底部列出的特殊事件参数 classes。接下来是我们的 3 个事件(如上所列),这些事件将用于向下载器发出更改信号。
接下来我们定义字段。
Thread thread;
这是对调用“Start()”方法时将创建的线程的引用。
bool aborting = false;
这是一个简单的标志,用于向线程发出我们应该中止的信号。现在我决定使用一个标志并让线程优雅地完成,而不是调用 Thread.Abort()
方法。这确保所有清理工作都能正常进行。
string[] addres =....
我们的初始地址。
到目前为止,这一切都很简单。接下来是我们的 Start()
方法。我提供了两种不同的方法。其中一种方法接受新的 string[]
地址下载(不那么重要)。
你会在这个方法中注意到
/// <summary>
/// Starts the downloader
/// </summary>
public void Start()
{
if (this.aborting)
return;
if (this.thread != null)
throw new Exception("Already downloading....");
this.aborting = false;
this.thread = new Thread(new ThreadStart(runDownloader));
this.thread.Start();
}
我们做的第一件事是检查是否设置了中止标志。如果是,则忽略开始调用(您可能会抛出异常)。接下来我们检查线程是否不为空。如果线程不为空,那么我们的下载器是 运行ning。最后,我们只需将我们的中止标志重置为 false
并开始我们新的 Thread
.
向下移动到 Abort()
方法。此方法将首先检查是否设置了 aborting
标志。如果是这样,那么什么都不做。接下来我们将 aborting
标志设置为 true。下一步,我会警告你这个 WILL 阻止你的调用线程是调用方法 Thread.Join()
,它将线程加入我们的调用线程。基本上等待线程退出。
最后我们简单地将线程实例设置为 null,将 aborting
标志重置为 false 并触发 Cancelled
事件(如果已订阅)。
接下来是执行下载的主要方法。首先你会注意到我移动了你的变量并对一次性对象使用了 using
语句。 (那是另外一个话题了)
runDownloader()
方法的重要部分是它定期检查 'aborting' 标志。如果此标志曾经设置为 true
,则 downloader
会停在那里。现在请注意,在 WebClient
下载图像时,您可能会遇到这样一种情况,即调用中止。理想情况下,您会让 WebClient
完成请求,正确处理然后退出循环。
每次下载图片后都会触发进度事件(如果已订阅)。最后,当迭代完成并且所有图像都已下载时,"Completed" 事件随编译的位图图像一起触发。
暂停呼吸
现在这一切都很棒..但是你如何使用它。很简单,我创建了一个带有按钮、进度条和图片框的表单。该按钮将用于启动和停止下载器,进度条用于处理进度事件和完成图像的图片框。
这是一个示例程序,我已经对其中的部分内容进行了评论。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.progressBar1.Visible = false;
this.progressBar1.Enabled = false;
}
Downloader downloader;
/// <summary>
/// starts \ stop button pressed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
//if downloader is not null then abort it
if (downloader != null)
{
downloader.Abort();
return;
}
//setup and start the downloader
this.progressBar1.Value = 0;
this.progressBar1.Minimum = 0;
this.progressBar1.Enabled = true;
this.progressBar1.Visible = true;
this.downloader = new Downloader();
this.downloader.Progress += downloader_Progress;
this.downloader.Completed += downloader_Completed;
this.downloader.Cancelled += downloader_Cancelled;
this.downloader.Start();
}
/// <summary>
/// downloader cancelled event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Cancelled(object sender, EventArgs e)
{
this.unhookDownloader();
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
MessageBox.Show(this, "Cancelled");
});
else
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
MessageBox.Show(this, "Cancelled");
}
}
/// <summary>
/// downloader completed event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Completed(Downloader sender, Downloader.DownloaderCompletedEventArgs e)
{
this.unhookDownloader();
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
this.pictureBox1.Image = e.Bitmap;
MessageBox.Show(this, "Completed");
});
else
{
this.progressBar1.Enabled = false;
this.progressBar1.Visible = false;
this.pictureBox1.Image = e.Bitmap;
MessageBox.Show(this, "Completed");
}
}
/// <summary>
/// downloader progress event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void downloader_Progress(Downloader sender, Downloader.DownloaderProgressEventArgs e)
{
if (this.progressBar1.InvokeRequired)
this.progressBar1.Invoke((MethodInvoker)delegate
{
this.progressBar1.Value = e.Completed;
this.progressBar1.Maximum = e.Total;
});
else
{
this.progressBar1.Value = e.Completed;
this.progressBar1.Maximum = e.Total;
}
}
/// <summary>
/// unhooks the events handlers and sets the downloader to null
/// </summary>
void unhookDownloader()
{
this.downloader.Progress -= downloader_Progress;
this.downloader.Completed -= downloader_Completed;
this.downloader.Cancelled -= downloader_Cancelled;
this.downloader = null;
}
}
这是如何使用 Thread
对象完成工作的简单实现。在我看来,这是太多的工作。现在让我们在 Background Worker 中完成它。
我强烈推荐这种方法
为什么你会说?后台工作者为我们提供了我们试图实现的 tested 和 supported 方法(以及更多)。您会注意到下载图像和检测取消的实际工作是相对相同的。但是您会注意到,在发布已完成和进度事件时,我并不(那么)担心跨线程问题。
private void button2_Click(object sender, EventArgs e)
{
if (this.worker != null && this.worker.IsBusy)
{
this.worker.CancelAsync();
return;
}
string[] addr = new string[] {".... our full addresss lists" };
this.progressBar1.Maximum = addr.Length;
this.progressBar1.Value = 0;
this.progressBar1.Visible = true;
this.progressBar1.Enabled = true;
this.worker = new BackgroundWorker();
this.worker.WorkerSupportsCancellation = true;
this.worker.WorkerReportsProgress = true;
this.worker.ProgressChanged += (s, args) =>
{
this.progressBar1.Value = args.ProgressPercentage;
};
this.worker.RunWorkerCompleted += (s, args) =>
{
this.progressBar1.Visible = false;
this.progressBar1.Enabled = false;
if (args.Cancelled)
{
MessageBox.Show(this, "Cancelled");
worker.Dispose();
worker = null;
return;
}
var img = args.Result as Bitmap;
if (img == null)
{
worker.Dispose();
worker = null;
return;
}
this.pictureBox1.Image = img;
MessageBox.Show(this, "Completed");
worker.Dispose();
worker = null;
};
this.worker.DoWork += (s, args) =>
{
Bitmap favCollection = new Bitmap(96, 64);
Graphics g = Graphics.FromImage(favCollection);
for (var i = 0; i < addr.Length; i++)
{
if (worker.CancellationPending)
break;
using (System.Net.WebClient client = new System.Net.WebClient())
{
try
{
byte[] dl = client.DownloadData(addr[i]);
using (var stream = new MemoryStream(dl))
{
using (var dlImg = new Bitmap(stream))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
catch (Exception)
{
using (var dlImg = new Bitmap(Properties.Resources.defaultFacIcon))
{
g.DrawImage(dlImg, (i % 6) * 16, (i / 6) * 16, 16, 16);
}
}
}
if (worker.CancellationPending)
break;
this.worker.ReportProgress(i);
}
if (worker.CancellationPending)
{
g.Dispose();
favCollection.Dispose();
args.Cancel = true;
return;
}
args.Cancel = false;
args.Result = favCollection;
};
worker.RunWorkerAsync();
}
我希望这有助于理解一些可能的方法来实现您想要实现的目标。
-妮可
我会考虑为此使用 Microsoft 的 Reactive Framework。它自动处理后台线程,并将非常有效地清理所有一次性引用。 NuGet "Rx-Main" & "Rx-WinForms"/"Rx-WPF".
首先,从您的地址数组开始:
var addr = new []
{
"http://google.com/favicon.ico",
// DELETED FOR BREVITY
"http://evernote.com/favicon.ico",
};
现在,定义一个查询以异步获取图像:
var query =
from a in addr.ToObservable().Select((url, i) => new { url, i })
from dl in Observable
.Using(
() => new System.Net.WebClient(),
wc => Observable.FromAsync(() => wc.DownloadDataTaskAsync(a.url)))
from bitmap in Observable
.Using(
() => new System.IO.MemoryStream(dl),
ms => Observable.Start(() => new Bitmap(ms)))
.Catch(ex => Observable.Return(new Bitmap(Properties.Resources.defaultFavIcon)))
select new { x = (a.i % 6) * 16, y = (a.i / 6) * 16, bitmap };
最后,等待所有图像都进来,然后在UI线程上,创建合成图像并将其分配给passAddDisplay
控件。
query
.ToArray()
.ObserveOn(passAddDisplay)
.Subscribe(images =>
{
var favCollection = new Bitmap(96, 64);
using(var g = Graphics.FromImage(favCollection))
{
foreach (var image in images)
{
g.DrawImage(image.bitmap, image.x, image.y, 16, 16);
image.bitmap.Dispose();
}
}
passAddDisplay.Image = favCollection;
});
我测试了查询,它工作正常。