Reputation: 91
development environment:
C#, visual studio 2010 (.net 4.0), win7 x64
codes in winform project:
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 function\r\n");
}
}
}
static void wc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
WriteLog("C:\\1.log", "callback function\r\n");
}
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();
}
then the log will be :
2017/11/03 19:04:48 main function
2017/11/03 19:04:51 main function
2017/11/03 19:04:54 main function
2017/11/03 19:04:57 main function
2017/11/03 19:05:00 main function
2017/11/03 19:05:03 main function
2017/11/03 19:05:06 main function
2017/11/03 19:05:09 main function
2017/11/03 19:05:12 main function
2017/11/03 19:05:15 main function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
2017/11/03 19:05:15 callback function
if same codes in ConsoleApplication:
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 function\r\n");
}
}
}
static void wc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
WriteLog("C:\\1.log", "callback function\r\n");
}
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();
}
then the log will be:
2017/11/03 19:13:50 callback function
2017/11/03 19:13:52 main function
2017/11/03 19:13:53 callback function
2017/11/03 19:13:55 main function
2017/11/03 19:13:56 callback function
2017/11/03 19:13:58 main function
2017/11/03 19:13:59 callback function
2017/11/03 19:14:01 main function
2017/11/03 19:14:02 callback function
2017/11/03 19:14:04 main function
2017/11/03 19:14:05 callback function
2017/11/03 19:14:08 main function
2017/11/03 19:14:08 callback function
2017/11/03 19:14:11 main function
2017/11/03 19:14:11 callback function
2017/11/03 19:14:14 main function
2017/11/03 19:14:14 callback function
2017/11/03 19:14:17 main function
2017/11/03 19:14:17 callback function
2017/11/03 19:14:20 main function
Obviously, the second result is right.
But in the first project, why the DownloadFileCompleted event isn't called untill all the downloads are finished?
How to call the DownloadFileCompleted event immediately after each download is done?
Upvotes: 2
Views: 2099
Reputation: 1533
I think the problem is that the DownloadFileCompleted
event is put into the same event queue as the Load
event of the form, so it cannot be handled until Form1_Load
completes.
In general, blocking the UI thread (like sleeping in Form1_Load
) is a bad practice. Time-consuming code should be run in a background thread. Below is one way to do that:
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 function\r\n");
}
}
}).Start();
}
Using this method, the background thread will not exit even after the form is closed (and the application will terminate only after the background thread finishes its work), which may or may not be what you want.
If you want the background thread to terminate when the form is closed, you can use a BackgroundWorker, which also gives you other convenient functionalities like reporting progress and canceling:
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 function\r\n");
}
}
};
worker.RunWorkerAsync();
}
Upvotes: 2
Reputation: 48
I recommend using an approach like from this other question "How to implement a Timeout on WebClient.DownloadFileAsync"
It looks like the thread.sleep is causing the thread writing the log to sleep. This way you won't have that happening, and it is non blocking so no thread is ever stuck busy waiting.
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);
Upvotes: 1