【问题标题】:UI freezes while File-Download with WebClient使用 WebClient 下载文件时 UI 冻结
【发布时间】:2015-11-04 09:45:44
【问题描述】:

我正在尝试使用 WebClient.DownloadFileAsync-Method 下载一些文件。 只要不显示 UI,它就可以正常工作。

UI 是一个带有标签和进度条的表单。

在 DownloadProgressChanged-Event 中,我想显示当前进度。 为了做到这一点,我调用了一个带有 int 参数的方法。 以下是下载方法:

private void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    if(progressDialog!=null){
        progressDialog.setFileProgress(e.ProgressPercentage);
    }
    Trace.WriteLine(String.Format("downloaded {0} of {1} bytes. {2} % complete...", 
        e.BytesReceived, 
        e.TotalBytesToReceive,
        e.ProgressPercentage));
}


private void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{               
    if(progressDialog!=null){               
        progressDialog.setFileProgress(100);
    }
    are.Set();
}


private AutoResetEvent are = new AutoResetEvent(false);
public void DownloadFiles(List<DownloadObject> objects){
    Trace.WriteLine("Start Download");
    wc.DownloadProgressChanged += DownloadProgressChanged;
    wc.DownloadFileCompleted += DownloadFileCompleted;
    try{ 
        foreach(DownloadObject dlo in objects){                 
            currentFile = dlo;
            String url = dlo.DownloadURL;
            String path = dlo.LocalPath;
            Uri uri = new Uri(url);
            //GET
            Thread thread = new Thread(() => wc.DownloadFileAsync(uri,path));
            //thread.SetApartmentState(ApartmentState.STA);
            thread.Start();                 
            are.WaitOne();
            DeleteFile(dlo.ID);
        }               
        Trace.WriteLine("FileDownload finished");
    }catch(Exception ex){
        Trace.WriteLine("FileDownload failed: "+ex.Message);
    }finally{
        wc.Dispose();   
    }           
}

这些是 ProgressDialog-Form 中的相关方法:

public delegate void dummy();
public void setFileProgress(int progress){          
    if(prgFile.InvokeRequired){
        Trace.WriteLine("Invoke required");
        prgFile.Invoke(new dummy(() => prgFile.Value = progress));
    }else{              
        Trace.WriteLine("Invoke not required");             
        prgFile.Value = progress;
    }           
}

public static ProgressDialog getInstance(IWin32Window owner){
    ProgressDialog pd = new ProgressDialog();
    //pd.Show(owner); //
    return pd;
}

现在发生的事情是这样的: 如果不调用 pd.Show() 一切都很好。进度发生了变化,我得到了“不需要调用”的输出以及下载的每个步骤。

但是,如果调用 pd.Show(),我会多次获得输出“需要调用”,而两者之间没有任何下载消息。

所以我调试了那部分代码,似乎调用了progressDialog.setFileProgress(),但在调用prgFile.Invoke-method 后,DownloadProgressChanged-Event 再次触发。

如果我将 Invoke 调用切换到 BeginInvoke,我会再次收到所有正确的消息,但 ProgressDialog 会冻结,直到所有下载完成并且我没有显示任何进度。

我在那里缺少什么?我阅读了大量关于此的问题和线程,但无法运行。

我正在使用带有 .Net-Framework 4.0 的 SharpDevelop

【问题讨论】:

    标签: c# download webclient


    【解决方案1】:

    因为您的“DownloadFiles”方法是同步的,这会阻塞 UI 线程。您启动一个新线程来下载文件,但是

    are.WaitOne();
    

    阻塞当前线程(UI线程)直到are被设置(在下载完成事件中)。

    正确的解决方案是把下载后的那些工作(例如DeleteFile)放到完整的事件中。

    【讨论】:

    • 问题是,DeleteFile 方法会删除服务器上的文件。因此,如果 DownloadFileAsync 尚未完成,我可能会遇到问题。我还需要使用 Asnyc,因为没有它我将无法获得进度信息,但在删除文件之前需要等待。如果没有 AutoResetEvent,我怎么能做到这一点?如果我不等待下载完成,我会收到一个错误,即 webclient 无法同时下载多个文件。
    • 某处我读到每个文件都应该有一个网络客户端,但我认为如果我这样做,我的 ProgressDialog 会爆炸。
    • 现在我尝试将删除放入完整的事件中,它发生了我所预测的。首先我收到消息说 WebClient 不能同时下载多个文件。然后我为每个文件创建了一个新的 web 客户端,从而解除了对 ProgressDialog 的阻止,但是进度条看起来像一个迪斯科球。也许我应该重新考虑 ProgressDialog 并在数据网格或其他东西中显示多个文件。
    • 你可以await异步方法调用。
    • 我试过了。看来你已经等不及了。这不是一个好的解决方案,但我通过使用标志并每 100 毫秒检查一次是否使用 Task.Delay(100) 来解决它
    【解决方案2】:

    因为DownloadFiles-方法是同步的并且在UI线程中运行。因此,如果您执行该方法,您的 UI 线程将被阻塞,直到该方法完成。

    对此有两种方法:

    多线程

    只需让方法在不同的任务中运行即可解决您的 UI 冻结问题。但是,正如您所提到的,在您的情况下,这将是一种可怕的方法,因为您无法跨线程操作 UI。如果你想进行状态更新,你不应该这样做。

    异步/等待

    这里有更好的方法。虽然看起来很简单,但 async/await 实际上很难做对。互联网上有很多资源,我建议从dotnetperls开始,这是对async/await的详细介绍。这需要一些时间,但绝对是最好的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多