簡體   English   中英

BackgroundWorker仍然凍結UI

[英]BackgroundWorker still freezes UI

我有以下代碼,如您所見,后台工作人員正在搜索文件,並且在進度更改中將事件文件添加到列表視圖中,但是由於有很多文件添加到列表視圖中,因此UI變得無響應,可以讓線程在循環中休眠,但是我認為這不是一個好習慣,防止UI凍結的最佳方法是什么?

詳細說明,listview是表單上的表單控件。

void bg_DoWork(object sender, DoWorkEventArgs e)
{
    Stack<string> dirs = new Stack<string>(20);
    dirs.Push(e.Argument.ToString());
    while (dirs.Count > 0)
    {
        string currentDir = dirs.Pop();
        string[] subDirs;
        try { subDirs = System.IO.Directory.GetDirectories(currentDir); }
        catch (UnauthorizedAccessException) { continue; }
        catch (System.IO.DirectoryNotFoundException) { continue; }

        string[] files = null;
        try { files = System.IO.Directory.GetFiles(currentDir); }

        catch (UnauthorizedAccessException) { continue; }
        catch (System.IO.DirectoryNotFoundException) { continue; }
        foreach (var file in files) { bg.ReportProgress(0, file); }
        foreach (string str in subDirs) { dirs.Push(str); }
    }
}
    void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        listView1.Items.Add(e.UserState.ToString());
    }

因此,這里的問題是ReportProgress實際上是異步的。 在繼續進行工作之前,它不會等待相應的UI更新真正完成。 通常這很好。 在大多數情況下,沒有令人信服的理由只是為了等待UI更新而降低生產效率。

但是有一個例外。 如果您如此頻繁地調用ReportProgress以至於在添加下一個進度更新之前實際上沒有時間來完成上一個進度更新,那么最終的結果是您在消息隊列中填充了進行更新進度的請求。 您有很多文件,並且獲取這些文件列表只需要很少的時間。 實際上,與編組UI線程和更新UI相比,它花費的時間要少得多。

由於最終會備份該隊列,因此任何其他UI更新都需要經過較長的隊列才能執行任何操作。

一種可能的解決方案是分批更新並較少地指示進度 鑒於您的情況,它可能會接受也可能不會接受。 幾乎可以肯定會有所幫助,但是取決於您根據正在執行的操作更新UI所需的時間以及生成數據的速度,甚至可能會導致問題。 如果它適合您的特定情況,那就太好了。

另一個選項是更改更新進度的方式,以使工作人員在繼續之前等待UI更新。 顯然,除非要這樣做,否則您應該避免這種情況,因為這意味着雖然您在工作時不凍結UI,但是您的工作將花費更長的時間。 盡管有許多方法可以做到這一點,但最簡單的方法可能只是使用Invoke而不是 BeginInvoke ):

foreach (var file in files)
    listView1.Invoke(new Action(()=>listView1.Items.Add(file));

雖然從BackgroundWorker調用Invoke通常是代碼異味,應該避免,但這是一種例外情況。

請注意,即使最終訴諸使用Invoke這里我還是建議拌和所調用,這樣你添加超過每調用只有一個項目的更多。 如果單個目錄中的文件數量足夠少,則將整個foreach放入Invoke ,並且如果您的子目錄往往只有很少的文件(即,它們很深,不是很寬),請考慮甚至將所有文件放入進入臨時列表,直到它足夠大以至於值得將其批量添加到Invoke 根據您的數據嘗試不同的方法,以了解最有效的方法。

bg.ReportProgress()旨在將BackgroundWorker的總體進度報告回UI線程,因此您可以將進度告知用戶。 但是,您正在使用它實際將字符串添加到ListView。 最好將文件列表編譯為內存列表,然后在后台工作程序完成時填充一次listView1:

public void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   foreach (var file in MyFileListVar){
       listView1.Items.Add(file);
   }
}

嘗試加載多個文件(比如說10到50之間),然后將它們發送回UI線程(即bg.ReportProgress ),而不是分別發送每個文件。

您不僅應該使用RunWorkerCompleted事件處理程序將項目添加到ListView,還應該一次調用AddRange而不是多次調用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM