JQY
JQY

Reputation: 91

DownloadFileCompleted doesn't work immediately after a download-completed event in C# winform?

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

Answers (2)

Imperishable Night
Imperishable Night

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

Ryan Donahue
Ryan Donahue

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

Related Questions