Tae*_*rin 10 c# wpf multithreading
在你将我的问题标记为重复之前,请听我说.
大多数人都在进行长时间运行的非UI操作,并且需要取消阻止UI线程.我有一个长时间运行的UI操作,必须在阻止我的应用程序的其余部分的UI线程上运行.基本上,我是DependencyObject
在运行时动态构建s并将它们添加到我的WPF应用程序上的UI组件.DependencyObject
需要创建的s 数取决于用户输入,其中没有限制.我有一个测试输入有大约6000 DependencyObject
秒需要创建并加载它们需要几分钟.
在这种情况下使用后台工作程序的常用解决方案不起作用,因为一旦DependencyObject
s由后台工作程序创建,它们就不能再添加到UI组件,因为它们是在后台线程上创建的.
我目前尝试解决方案是在后台线程中运行循环,为每个工作单元调度到UI线程,然后调用Thread.Yield()
以给UI线程更新机会.这几乎可以工作 - UI线程确实有机会在操作期间多次更新自己,但应用程序仍然基本上被阻止.
在长时间运行的操作过程中,如何让我的应用程序继续更新UI并在其他表单上处理事件?
编辑:根据要求,我当前的"解决方案"的一个例子:
private void InitializeForm(List<NonDependencyObject> myCollection)
{
Action<NonDependencyObject> 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);
}
Run Code Online (Sandbox Code Playgroud)
更改Thread.Yield()
到Thread.Sleep(1)
似乎工作,但真正好的解决办法?
nos*_*tio 10
有时确实需要在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++;
}
}
Run Code Online (Sandbox Code Playgroud)
这将使UI保持响应,并将尽可能快地完成后台工作,但具有空闲优先级.
我们可能希望通过一些限制来增强它(在迭代之间等待至少100 ms)和更好的取消逻辑:
async Task DoUIThreadWorkAsync(CancellationToken token)
{
Func<Task> idleYield = async () =>
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
var cancellationTcs = new TaskCompletionSource<bool>();
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++;
}
}
}
Run Code Online (Sandbox Code Playgroud)
已更新OP已发布示例代码.
根据您发布的代码,我同意@ HighCore关于正确ViewModel的评论.
你当前正在这样做的方式,background.BeginInvoke
在池线程上启动后台操作,然后在紧密foreach
循环上同步回调UI线程Dispatcher.Invoke
.这只会增加额外的开销.此外,你没有观察到这个操作的结束,因为你只是忽略了IAsyncResult
返回的background.BeginInvoke
.因此,InitializeForm
返回,同时background.BeginInvoke
继续在后台线程.从本质上讲,这是一个即发即弃的电话.
如果你真的想坚持使用UI线程,下面是使用我描述的方法完成它的方法.
请注意,尽管它在UI线程上发生,但_initializeTask = background()
仍然是异步操作.如果没有嵌套的Dispatcher事件循环,你将无法使它同步InitializeForm
(由于UI重入的含义,这将是一个非常糟糕的主意).
也就是说,简化版(无油门或取消)可能如下所示:
Task _initializeTask;
private void InitializeForm(List<NonDependencyObject> myCollection)
{
Action<NonDependencyObject> doWork = (nonDepObj) =>
{
var dependencyObject = CreateDependencyObject(nonDepObj);
UiComponent.Add(dependencyObject);
// Set up some binding on each dependencyObject and update progress bar
...
};
Func<Task> background = async () =>
{
foreach (var nonDependencyObject in myCollection)
{
if (nonDependencyObject.NeedsToBeAdded())
{
doWork(nonDependencyObject);
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
}
}
};
_initializeTask = background();
}
Run Code Online (Sandbox Code Playgroud)