【问题标题】:Why UI is freeze and not display one by one?为什么UI卡住了,不能一一显示?
【发布时间】:2020-01-16 15:11:23
【问题描述】:

我有一个按钮,点击它将处理所有文件。我想在运行时显示进度。因此,如果文件 1 正在处理,则 UI 显示

“处理文件1”

;处理文件 2 时,UI 显示

“处理文件1”

“处理文件2”

所以我使用 Listbox 来完成。 ListBox 的 ItemSource 是我的 ViewModel 中的一个集合

private ObservableCollection<string> _displayedFiles;
public  ObservableCollection<string> DisplayedFiles
{
    get {return _displayedFiles;}
    set 
    {
        _displayedFiles = value;
        PropertyChanged(nameof(DisplayedFiles));
    }
}

现在 ViewModel 被传递给命令类

public class MyCommand :ICommand
{
    private MyViewModel myViewModel;
    public MyCommand(MyViewModel myViewModel)
    {
       this.myViewModel = myViewModel;
    }

   public void Execute(object parameter)
   {
        foreach(var f in files)
        {
          Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate () { AddFiles(f);});
        }
    }

    private void AddFiles(string f)
    {
        this.ViewModel.DisplayedFiles.Add(f);
    }
}

但是我发现 UI 卡住了,列表没有一一显示。循环迭代完成后,它会一起显示整个列表。

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    当您使用 Dispatcher.Invoke 时 - 您基本上是在告诉 GUI 线程做某事。 由于您在该线程中进行繁重的工作,因此 GUI 将冻结。

    为了使进度条正常工作,您总是在不同的线程上花费大量时间完成任务,并且只在 GUI 线程上进行进度条 GUI 更新。

    让我给你看一个基本的伪例子:

    想象一下我的代码是:

    private void DoTheTaskAndUpdateGUI(List<string> filesToProcess)
    {
        int completionPct = 0;
        for(int idx = 0;idx<filesToProcess.Count; idx++)
        {
            ProcessFile(filesToProcess[idx]);
            completionPct = ((idx + 1)/filesToProcess.Count) * 100;
            UpdateGUI(completionPct);
        }
    }
    

    如果所有这些都在单个 GUI 线程上运行,我的 UI 将一直冻结,直到循环结束。为了让 UI 有点响应,我可以重新排列功能,例如:

    private void DoTheTaskAndUpdateGUI(List<string> filesToProcess)
    {
        var guiDispatcher = Dispatcher.CurrentDispatcher;
            Task.Factory.StartNew(()=> 
            {
                int completionPct = 0;
                for(int idx = 0;idx<filesToProcess.Count; idx++)
                {
                    ProcessFile(filesToProcess[idx]);
                    completionPct = ((idx + 1)/filesToProcess.Count) * 100;
                    guiDispatcher.Invoke(UpdateGUI(completionPct));
                }
            });
    }
    

    我已经在记事本中编写了这段代码,所以不要假设它编译成功。但了解其背后的想法。

    • 在 GUI 线程上调用 DoTheTaskAndUpdateGUI 方法

    • 我们持有 Dispatcher 实例

    • 一个新的线程/任务启动和繁重的工作 从它开始
    • GUI 线程现已免费
    • 每当密集型任务 到达 Dispatcher.Invoke 调用它通过请求 GUI 更新 GUI 线程这样做

    【讨论】:

    • 即使我删除了Dispatcher.Invoke,也是一样的。
    • 这是因为 Execute 函数本身是从 GUI 线程调用的。阅读任务或线程类。请记住,任何 GUI 更新都必须在 GUI 线程上完成,因此那里需要 Dispatcher.Invoke。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-08
    • 2016-11-01
    • 2022-08-16
    • 2023-02-18
    相关资源
    最近更新 更多