1

开发环境:

C#、视觉工作室 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 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();
    }

那么日志将是:

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 回调函数

如果 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();
    }

那么日志将是:

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 事件?

4

2 回答 2

2

我认为问题在于该DownloadFileCompleted事件被放入与Load表单事件相同的事件队列中,因此在完成之前无法处理Form1_Load

一般来说,阻塞 UI 线程(比如 sleep in 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 function\r\n");

            }
        }
    }).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 function\r\n");

            }
        }
    };
    worker.RunWorkerAsync();
}
于 2017-11-03T12:30:33.747 回答
1

我建议使用类似另一个问题“如何在 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);
于 2017-11-03T12:00:10.340 回答