【问题标题】:DownloadFileCompleted doesn't work immediately after a download-completed event in C# winform?在 C# winform 中的下载完成事件后,DownloadFileCompleted 不能立即工作?
【发布时间】:2017-11-03 11:33:33
【问题描述】:

开发环境:

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

【问题讨论】:

  • 其实两个都对:)
  • 似乎 DownloadFileCompleted 回调被第一个项目中的某些东西阻止了,结果不是我想要的。 winform项目中如何得到第二个结果?
  • @JQY 为什么你的wc_DownloadFileCompleted处理程序在winform项目中是静态的?
  • DownloadFileCompleted 事件将努力使您的事件处理程序线程安全。所以你可以在那个事件处理程序中做一些不会让你的程序崩溃的事情,比如更新控件。它可以在 Winforms 应用程序中执行此操作,这要归功于非空 SynchronizationContext.Current,它无法在控制台模式应用程序中执行此操作。结果是,只要你让 UI 线程在 Thread.Sleep() 调用和 for 循环中变得紧张,什么都不会发生。在您的 Load 事件处理程序完成之前,现在这些事件会迅速触发。功能,而不是错误。永远不要挂起 UI 线程。
  • @Hans Passant 感谢您详细的回复,对我找出问题所在非常有帮助。

标签: c# winforms webclient downloadfileasync


【解决方案1】:

我认为问题在于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 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();
}

【讨论】:

  • 得到它。答案很有帮助,BackgroundWorker 也很有用。非常感谢^_^
  • //我确信下载将在 3 秒内完成 - 像这样的代码经常使其投入生产
  • @ThomasWeller 好吧,在下载任务上设置超时并不是一个坏习惯。虽然这可能是错误的做法......(不过,作为一个最小的工作示例效果很好;比给出一个实际上需要 3 秒才能下载的 URL 更好。)
【解决方案2】:

我建议使用其他问题“How to implement a Timeout on 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);

【讨论】:

  • 哦,是的,也许 await 函数是控制异步方法处理时间的好方法,谢谢^_^
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-14
  • 2015-08-30
相关资源
最近更新 更多