在执行大量PropertyUpdates时避免UI锁定

时间:2015-03-16 15:05:13

标签: c# wpf multithreading performance dispatcher

当您一次对UI进行大量更新时,我试图找到避免UI锁定的最佳方法。

基本前提是,在启动时,我的工具在后台工作程序中运行perforce FSTAT。这会生成一个非常大的文件列表及其信息。完成此操作后,在其RunWorkerCompleted函数中,我将此信息传播到TreeView内的UI。

然而,这涉及到大量的财产更新!取决于其传播到的文件数。它可以是5000多个文件。这完全锁定了用户界面大约3-5秒。

我想知道我是否可以异步更新用户界面,我说,一次传播10-20个文件&仍然让UI线程继续更新,以便它仍然响应。

谢谢。

2 个答案:

答案 0 :(得分:2)

如果要使用属性绑定更新TreeView内部的信息,可以将Binding.IsAsync标志设置为true。如果您没有使用绑定更新值,那么可能需要考虑这些。

Binding.IsAsync Property

另一种选择是更新所有属性,但不要为属性调用PropertyChanged事件(假设您使用INotifyPropertyChanged更新绑定),直到所有数据都被更改,然后调用PropertyChanged对于任务上的每个属性的事件,它仍然是异步,但即使有5000+绑定更新,也不应该花3-5秒。

答案 1 :(得分:1)

你提出的很多建议终于让我得到了一个很好的答案。这是我的代码如下。基本上我们可以使用ReportProgress来允许UI在运行时进行更新。然后调整我们希望这种情况发生的频率。以下是我的解决方案。

关键是每N个项目调用PropegateMetaData(我指定了25个)。然后列表被清空。

这将调用每25个项目的报告进度,然后继续。并最终将其余部分传递给WorkerCompleted。

    public static void Refresh(List<string> refreshPaths)
    {
        BackgroundWorker worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;

        worker.DoWork += delegate(object s, DoWorkEventArgs args)
        {
            List<string> filesPath = null;
            if (refreshPaths == null)
            {
                filesPath = DatabaseViewModel.Instance.Records.Select(record => record.Filepath).ToList();
            }
            else
            {
                filesPath = new List<string>(refreshPaths);
            }

            if (m_Repository != null && filesPath.Count > 0)
            {
                IList<FileSpec> lfs = new List<FileSpec>();

                int index = 0;
                foreach (DataRecord rec in DatabaseViewModel.Instance.Records)
                {
                    lfs.Add(new FileSpec(new LocalPath(rec.Filepath), null));
                    index++;
                    if (index > MaxFilesIteration)
                    {
                        GetFileMetaDataCmdOptions opts = new GetFileMetaDataCmdOptions(GetFileMetadataCmdFlags.AllRevisions, null, null, 0, null, null, null);
                        worker.ReportProgress(0, m_Repository.GetFileMetaData(lfs, null));
                        lfs.Clear();
                        index = 0;
                    }
                }

                args.Result = m_Repository.GetFileMetaData(lfs, null); //pass the remaining results across
            }
        };

        worker.ProgressChanged += (sender, args) => PropegateMetaData(args.UserState as IList<FileMetaData>);
        worker.RunWorkerCompleted += (sender, args) => PropegateMetaData(args.Result as IList<FileMetaData>);
        worker.RunWorkerAsync();
    }

    private static void PropegateMetaData(IList<FileMetaData> fileList)
    {
        IList<FileMetaData> fileState = fileList as IList<FileMetaData>;
        if (fileState != null)
        {
            foreach (FileMetaData fmd in fileState)
            {
                DataRecord currentRecord = DatabaseViewModel.Instance.GetRecordByFilepath(fmd.LocalPath.Path);
                if (currentRecord != null)
                {
                    switch (fmd.Action)
                    {
                        case FileAction.Add:
                            currentRecord.P4Status = P4FileState.Added;
                            break;
                        case FileAction.Edit:
                            currentRecord.P4Status = P4FileState.Edit;
                            break;
                        case FileAction.MoveAdd:
                            currentRecord.P4Status = P4FileState.MoveAdd;
                            break;
                        default:
                            currentRecord.P4Status = P4FileState.None;
                            break;
                    }
                }
            }
        }
    }