如何在长时间运行* UI *操作期间刷新UI

在你将我的问题标记为重复之前,请听我说。

大多数人都在进行长时间运行的非UI操作,并且需要取消阻止UI线程。 我有一个长期运行的UI操作, 必须在UI线程上运行,这阻止了我的应用程序的其余部分。 基本上,我是在运行时动态构造DependencyObject并将它们添加到我的WPF应用程序上的UI组件。 需要创建的DependencyObject的数量取决于用户输入,其中没有限制。 我有一个测试输入有大约6000个DependencyObject需要创建并加载它们需要几分钟。

在这种情况下使用后台工作程序的常用解决方案不起作用,因为一旦DependencyObject由后台工作程序创建,它们就不能再添加到UI组件,因为它们是在后台线程上创建的。

我目前尝试解决方案是在后台线程中运行循环,为每个工作单元分配到UI线程,然后调用Thread.Yield()以给UI线程提供更新的机会。 这几乎可以工作 – UI线程确实有机会在操作期间多次更新自己,但应用程序仍然基本上被阻止。

在长时间运行的操作过程中,如何让我的应用程序继续更新UI并在其他表单上处理事件?

编辑:根据要求,我当前的“解决方案”的一个例子:

 private void InitializeForm(List myCollection) { Action doWork = (nonDepObj) => { var dependencyObject = CreateDependencyObject(nonDepObj); UiComponent.Add(dependencyObject); // Set up some binding on each dependencyObject and update progress bar ... }; Action background = () => { foreach (var nonDependencyObject in myCollection) { if (nonDependencyObject.NeedsToBeAdded()) { Dispatcher.Invoke(doWork, nonDependencyObject); Thread.Yield(); //Doesn't give UI enough time to update } } }; background.BeginInvoke(background.EndInvoke, null); } 

Thread.Yield()更改为Thread.Sleep(1)似乎可行,但这真的是一个很好的解决方案吗?

有时确实需要在UI线程上进行后台工作,特别是当大多数工作是处理用户输入时。

示例:实时语法突出显示,即按型。 可以将这种后台操作的一些子工作项卸载到池线程,但这并不能消除编辑器控件的文本在每个新键入的字符上发生变化的事实。

手头的帮助: await Dispatcher.Yield(DispatcherPriority.ApplicationIdle) 。 这将为用户输入事件(鼠标和键盘)提供WPF Dispatcher事件循环的最佳优先级。 后台工作流程可能如下所示:

 async Task DoUIThreadWorkAsync(CancellationToken token) { var i = 0; while (true) { token.ThrowIfCancellationRequested(); await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); // do the UI-related work this.TextBlock.Text = "iteration " + i++; } } 

这将使UI保持响应,并将尽可能快地完成后台工作,但具有空闲优先级。

我们可能希望通过一些限制来增强它(在迭代之间等待至少100 ms)和更好的取消逻辑:

 async Task DoUIThreadWorkAsync(CancellationToken token) { Func idleYield = async () => await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); var cancellationTcs = new TaskCompletionSource(); using (token.Register(() => cancellationTcs.SetCanceled(), useSynchronizationContext: true)) { var i = 0; while (true) { await Task.Delay(100, token); await Task.WhenAny(idleYield(), cancellationTcs.Task); token.ThrowIfCancellationRequested(); // do the UI-related work this.TextBlock.Text = "iteration " + i++; } } } 

在OP发布示例代码时更新。

根据您发布的代码,我同意@ HighCore关于正确ViewModel的评论。

你当前正在这样做的方式, background.BeginInvoke在池线程上启动后台操作,然后使用Dispatcher.Invoke在紧密的foreach循环上同步回调UI线程。 这只会增加额外的开销。 此外,您没有观察到此操作的结束,因为您只是忽略background.BeginInvoke返回的IAsyncResult 。 因此, InitializeForm返回,而background.BeginInvoke在后台线程上继续。 从本质上讲,这是一个即发即弃的电话。

如果你真的想坚持使用UI线程,下面是使用我描述的方法完成它的方法。

请注意, _initializeTask = background()仍然是一个异步操作 ,尽管它发生在UI线程上。 如果没有在InitializeForm嵌套的Dispatcher事件循环,你将无法使它同步 (由于UI重新入侵的影响,这将是一个非常糟糕的主意)。

也就是说,简化版(无油门或取消)可能如下所示:

 Task _initializeTask; private void InitializeForm(List myCollection) { Action doWork = (nonDepObj) => { var dependencyObject = CreateDependencyObject(nonDepObj); UiComponent.Add(dependencyObject); // Set up some binding on each dependencyObject and update progress bar ... }; Func background = async () => { foreach (var nonDependencyObject in myCollection) { if (nonDependencyObject.NeedsToBeAdded()) { doWork(nonDependencyObject); await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); } } }; _initializeTask = background(); }