等待Dispatcher.InvokeAsync与Dispatcher.调用



我有一个带有按钮的WPF程序,它创建并显示一些数据绑定到网格的数据。创建数据的过程非常缓慢,并且受CPU限制,因此我将其卸载到任务中。我想在准备好后立即显示第一个数据块,然后显示第二个数据块。

这里有3个实现,它们都能工作并保持UI的响应性。

等待Dispatcher。InvokeAsync,Dispatcher。Invoke和Dispatcher。Invoke(在Task.Run中(。以下哪一项可以避免阻塞线程池中本来可以工作的线程,如果有人在程序的其他地方阻塞了UI线程,哪一项最不可能导致死锁?

public ObservableCollection<BigObject> DataBoundList {get;set;}
public ObservableCollection<BigObject> DataBoundList2 {get;set;}
//Click handler from WPF UI button
public async void ClickHandlerCommand()
{
List<BigObject> items1 = null;
List<BigObject> items2 = null;

//On UI Thread
await Task.Run(() =>
{
//On thread X from threadpool
items1 = SlowCPUBoundMethod1();

}).ConfigureAwait(false);
Dispatcher.Invoke(() => 
{ 
//On UI Thread
DataBoundList = new ObservableCollection<BigObject>(items1);
RaisePropertyChanged(nameof(DataBoundList));
});

//On thread X from threadpool
await Task.Run(() =>
{
//On thread Y from threadpool
items2 = SlowCPUBoundMethod2();

}).ConfigureAwait(false);

//On thread Y from threadpool
Dispatcher.Invoke(() => 
{ 
//On UI Thread
DataBoundList2 = new ObservableCollection<BigObject>(items2);
RaisePropertyChanged(nameof(DataBoundList2));
});
//On thread Y from threadpool
//5x context switches
}

上面的实现将调度程序调用置于Task之外。跑这可能会导致两条线被纺起来。如果程序中的另一个线程阻止了UI线程,那么我认为是Dispatcher。调用可能会死锁吗?

public async void ClickHandlerCommand2()
{
List<BigObject> items = null;
List<BigObject> items2 = null;
//On UI Thread 
await Task.Run(() =>
{
//On thread X from threadpool
items1 = SlowCPUBoundMethod1();

Dispatcher.Invoke(() => 
{ 
//On UI thread
DataBoundList = new ObservableCollection<BigObject>(items1);
RaisePropertyChanged(nameof(DataBoundList));
});
//On thread X from threadpool
items2 = SlowCPUBoundMethod2();

Dispatcher.Invoke(() => 
{ 
//On UI thread
DataBoundList2 = new ObservableCollection<BigObject>(items2);
RaisePropertyChanged(nameof(DataBoundList2));
});
//On thread X from threadpool

}).ConfigureAwait(false);
//On thread X from threadpool
//5x context switches
}

上面的实现将只有一个线程,但是,如果程序中的另一个线程阻止了UI线程,那么我认为是Dispatcher。调用可能会死锁吗?

public async void ClickHandlerCommand3()
{
List<BigObject> items1 = null;
List<BigObject> items2 = null;
//On UI Thread
await Task.Run(() =>
{
//On thread X from threadpool
items1 = SlowCPUBoundMethod1();

}).ConfigureAwait(false);
//On thread X from threadpool
await Dispatcher.InvokeAsync(() => 
{ 
//On UI Thread
DataBoundList = new ObservableCollection<BigObject>(items1);
RaisePropertyChanged(nameof(DataBoundList));
});


//On thread X from threadpool
items2 = SlowCPUBoundMethod2();
await Dispatcher.InvokeAsync(() => 
{ 
//On UI Thread
DataBoundList2 = new ObservableCollection<BigObject>(items2);
RaisePropertyChanged(nameof(DataBoundList2));
});
//On thread X from threadpool
//5x context switches
}

这应该只会导致一个任务被旋转,我相信如果其他地方的人阻塞了UI线程,可以降低死锁的风险。我认为这是最好的实现方式?

有人能明确地说哪一个是正确的实施吗?我相信第三个使用await Dispatcher的示例。InvokeAsync是正确的,但我不完全确定。

两个Dispatcher。Invoke和InvokeAsync在调度程序的线程上执行委托。前者同步执行,并将阻塞调用线程,直到委托完成执行;后者不会阻塞调用线程。

这两个方法都根据DispatcherPriority参数将委托排入调度程序处理队列中的某个位置(除非使用发送优先级,否则dispatcher.Invoke可能会绕过队列并立即调用委托(。因此,优先级越低,调用线程在等待完成时可能被阻塞的时间就越长(如果使用Dispatcher.Invoke(

第三种方法,任务。Run(((=>调度员。Invoke(((,不会阻塞原始调用线程,但它确实阻塞了运行任务的线程(可能是线程池线程(。

调度员。InvokeAsync是您的用例的最佳方法,它正是为此目的而设计的。

这不是对所问问题的回答,该问题是关于Dispatcher.InvokeDispatcher.InvokeAsync之间的差异。我想分享我个人对这两种方法的偏好,即两者都不使用。它们既丑陋又笨重,而且在很大程度上是多余的。Task.Run足以将工作卸载到ThreadPool,然后等待创建的Task<TResult>足以获取计算结果,并在UI线程上使用它:

public async void ClickHandlerCommand()
{
var items = await Task.Run(() => SlowCPUBoundMethod1());
DataBoundList = new ObservableCollection<BigObject>(items1);
RaisePropertyChanged(nameof(DataBoundList));
var items2 = await Task.Run(() => SlowCPUBoundMethod2());
DataBoundList2 = new ObservableCollection<BigObject>(items2);
RaisePropertyChanged(nameof(DataBoundList2));
}

在UI和后台线程之间需要更细粒度的通信的情况下,可以使用一个或多个IProgress<T>对象来建立这种通信。后台线程被传递一个IProgress<T>对象,并使用它以抽象的方式报告进度,UI线程接收这些进度通知并使用它们来更新UI。这里可以找到使用IProgress<T>接口的示例。这也是一篇很好的读物:4.5中的异步:在异步API中启用进度和取消。

最新更新