等待 Dispatcher.InvokeAsync 与 Dispatcher.Invoke [英] await Dispatcher.InvokeAsync vs Dispatcher.Invoke

查看:49
本文介绍了等待 Dispatcher.InvokeAsync 与 Dispatcher.Invoke的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a WPF program with a button which creates and displays some data which is databound to a grid. The process of creating the data is very slow and CPU bound hence I offload it to a task. I want to display the first chunk of data as soon as its ready, then display the second chunk.

这里有 3 个实现,它们都可以工作并保持 UI 响应.

Here are 3 implementations which all work and keep the UI responsive.

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

await Dispatcher.InvokeAsync, Dispatcher.Invoke and Dispatcher.Invoke (inside the Task.Run). Which of these is going to avoid blocking a thread on the threadpool that could otherwise be doing work, and which is the least likely to result in a deadlock if someone had blocked the UI thread elsewhere in the program?

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.Run 之外.这可能会导致启动两个线程.如果程序中的另一个线程阻塞了 UI 线程,那么我认为 Dispatcher.Invoke 调用可能会死锁?

The implementation above puts the dispatcher call outside the Task.Run. This will likely cause two threads to be spun up. If another thread someone in the program had blocked the UI thread then I think the Dispatcher.Invoke call would possibly deadlock?

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.Invoke 调用可能会死锁?

The implementation above will have a single thread, however if another thread someone in the program had blocked the UI thread then I think the Dispatcher.Invoke call would possibly deadlock?

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
}

这应该会导致只有 1 个任务被启动,我相信如果其他人阻止了 UI 线程,我相信会降低死锁的风险.我认为这是最好的实现?

This should result in only 1 task being spun up and I believe reduce the risk of a deadlock if someone somewhere else has blocked the UI thread. I think this is the best implementation?

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

Can someone categorically say which is the correct implementation? I believe the third example using await Dispatcher.InvokeAsync is the correct one but I'm not completely sure.

推荐答案

Dispatcher.Invoke 和 InvokeAsync 都在调度程序的线程上执行委托.前者是同步进行的,会阻塞调用线程,直到委托完成;后者不会阻塞调用线程.

Both Dispatcher.Invoke, and InvokeAsync execute the delegate on the dispatcher's thread. The former does this synchronously, and will block the calling thread until the delegate finishes execution; the latter doesn't block the calling thread.

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

Both methods enque the delegate somewhere in the dispatcher's processing queue, based on the DispatcherPriority param (unless you use send priority, then Dispatcher.Invoke may bypass the queue and invoke the delegate immediately). It follows then, that the lower the priority, the longer the calling thread may be blocked while waiting for it to complete (if you use Dispatcher.Invoke).

第三种方法,Task.Run(() => Dispatcher.Invoke()),不会阻塞原来的调用线程,但会阻塞运行任务的线程(大概是线程池线程)).

The third approach, Task.Run(() => Dispatcher.Invoke()), doesn't block the original calling thread, but it does block the thread on which the task is running (presumably a thread pool thread).

Dispatcher.InvokeAsync 是最适合您的用例的方法,它正是为此目的而设计的.

Dispatcher.InvokeAsync is the best approach for your use-case, it was designed for this exact purpose.

这篇关于等待 Dispatcher.InvokeAsync 与 Dispatcher.Invoke的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆