开发环境:
C#,visual studio 2010(.net 4.0),win7 x64
winform项目中的代码:
private void Form1_Load(object sender, EventArgs e)
{
string path = "c:\1.jpg";
for (int i = 0; i < 10; i++)
{
string url = "http://...." + i.ToString() + ".jpg";//i'm sure the http file does exist
using (WebClient wc = new WebClient())
{
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(wc_DownloadFileCompleted);
wc.DownloadFileAsync(new Uri(url), path);
Thread.Sleep(3000);//i'm sure the download will be finished in 3s
WriteLog("C:\1.log", "main functionrn");
}
}
}
static void wc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
WriteLog("C:\1.log", "callback functionrn");
}
static void WriteLog(string LogName, string log)
{
StreamWriter sw = new StreamWriter(LogName, true);
if (sw == null)
return;
sw.Write(System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + " " + log);
sw.Close();
}
则日志将为:
2017/11/03 19:04:48主要功能
2017/11/03 19:04:51主要功能
2017/11/03 19:04:54主要功能
2017/11/03 19:04:57主要功能
2017/11/03 19:05:00主要功能
2017/11/03 19:05:03主要功能
2017/11/03 19:05:06主要功能
2017/11/03 19:05:09主要功能
2017/11/03 19:05:12主要功能
2017/11/03 19:05:15主要功能
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
2017/11/03 19:05:15回调函数
如果控制台应用程序中的代码相同:
static void Main(string[] args)
{
string path = "c:\1.jpg";
for (int i = 0; i < 10; i++)
{
string url = "http://...." + i.ToString() + ".jpg";//i'm sure the http file does exist
using (WebClient wc= new WebClient())
{
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(wc_DownloadFileCompleted);
wc.DownloadFileAsync(new Uri(url), path);
Thread.Sleep(3000);//i'm sure the download will be finished in 3s
WriteLog("C:\1.log", "main functionrn");
}
}
}
static void wc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
WriteLog("C:\1.log", "callback functionrn");
}
static void WriteLog(string LogName, string log)
{
StreamWriter sw = new StreamWriter(LogName, true);
if (sw == null)
return;
sw.Write(System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + " " + log);
sw.Close();
}
则日志将为:
2017/11/03 19:13:50回调函数
2017/11/03 19:13:52主要功能
2017/11/03 19:13:53回调函数
2017/11/03 19:13:55主要功能
2017/11/03 19:13:56回调函数
2017/11/03 19:13:58主要功能
2017/11/03 19:13:59回调函数
2017/11/03 19:14:01主要功能
2017/11/03 19:14:02回调函数
2017/11/03 19:14:04主要功能
2017/11/03 19:14:05回调函数
2017/11/03 19:14:08主要功能
2017/11/03 19:14:08回调函数
2017/11/03 19:14:11主要功能
2017/11/03 19:14:11回调函数
2017/11/03 19:14:14主要功能
2017/11/03 19:14:14回调功能
2017/11/03 19:14:17主要功能
2017/11/03 19:14:17回调功能
2017/11/03 19:14:20主要功能
显然,第二个结果是正确的。
但是在第一个项目中,为什么在所有下载完成之前不调用DownloadFileCompleted事件?
如何在每次下载完成后立即调用DownloadFileCompleted事件?
我认为问题在于DownloadFileCompleted
事件与表单的Load
事件被放入同一事件队列中,因此在Form1_Load
完成之前无法处理。
通常,阻塞UI线程(就像在Form1_Load
中睡觉一样)是一种糟糕的做法。耗时的代码应该在后台线程中运行。以下是一种方法:
private void Form1_Load(object sender, EventArgs e)
{
new Thread(() =>
{
string path = "c:\1.jpg";
for (int i = 0; i < 10; i++)
{
string url = "http://...." + i.ToString() + ".jpg";//i'm sure the http file does exist
using (WebClient wc = new WebClient())
{
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(wc_DownloadFileCompleted);
wc.DownloadFileAsync(new Uri(url), path);
Thread.Sleep(3000);//i'm sure the download will be finished in 3s
WriteLog("C:\1.log", "main functionrn");
}
}
}).Start();
}
使用此方法,即使在表单关闭后,后台线程也不会退出(并且只有在后台线程完成工作后,应用程序才会终止),这可能是您想要的,也可能不是您想要的。
如果您希望后台线程在表单关闭时终止,您可以使用BackgroundWorker,它还为您提供了其他方便的功能,如报告进度和取消:
private void Form1_Load(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (sender_, e_) =>
{
string path = "c:\1.jpg";
for (int i = 0; i < 10; i++)
{
string url = "http://...." + i.ToString() + ".jpg";//i'm sure the http file does exist
using (WebClient wc = new WebClient())
{
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(wc_DownloadFileCompleted);
wc.DownloadFileAsync(new Uri(url), path);
Thread.Sleep(3000);//i'm sure the download will be finished in 3s
WriteLog("C:\1.log", "main functionrn");
}
}
};
worker.RunWorkerAsync();
}
我建议使用类似于另一个问题"如何在WebClient.DownloadFileAsync上实现超时"的方法
看起来thread.sleep导致写入日志的线程休眠。这样就不会发生这种情况,而且它是非阻塞的,所以不会有线程被困在繁忙的等待中。
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(5));
await Task.Factory.StartNew(() =>
{
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(wc_DownloadFileCompleted);
wc.DownloadFile(new Uri("MyInternetFile"), filePath);
}, source.Token);