首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >await Dispatcher.InvokeAsync vs Dispatcher.Invoke

await Dispatcher.InvokeAsync vs Dispatcher.Invoke
EN

Stack Overflow用户
提问于 2021-06-20 11:12:01
回答 2查看 165关注 0票数 0

我有一个WPF程序与一个按钮,创建和显示一些数据,这是数据绑定到网格。创建数据的过程非常慢,而且受CPU的限制,因此我将其卸载到一个任务中。我希望在第一个数据块准备就绪后立即显示它,然后显示第二个数据块。

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

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

代码语言:javascript
复制
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
}

上面的实现将dispatcher调用放在Task.Run之外。这可能会导致两个线程旋转起来。如果程序中的另一个线程阻塞了UI线程,那么我认为Dispatcher.Invoke调用可能会死锁?

代码语言:javascript
复制
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调用可能会死锁?

代码语言:javascript
复制
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的第三个示例是正确的,但我不能完全确定。

EN

回答 2

Stack Overflow用户

发布于 2021-06-20 12:43:55

Dispatcher.Invoke和InvokeAsync都在dispatcher的线程上执行委托。前者同步地执行此操作,并将阻塞调用线程,直到委托完成执行;后者不会阻塞调用线程。

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

第三种方法Task.Run(() => Dispatcher.Invoke())不会阻塞原始调用线程,但会阻塞正在运行任务的线程(假设是线程池线程)。

对于你的用例,Dispatcher.InvokeAsync是最好的方法,它就是为这个目的而设计的。

票数 3
EN

Stack Overflow用户

发布于 2021-06-20 15:30:27

这不是对关于Dispatcher.InvokeDispatcher.InvokeAsync之间的区别的问题的回答。我想在这两种方法之间分享我的个人偏好,那就是两者都不使用。它们都是丑陋、笨拙的,而且在大多数情况下都是多余的。Task.Run足以将工作卸载到ThreadPool,然后等待创建的Task<TResult>足以获取计算结果,并在UI线程上使用它:

代码语言:javascript
复制
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对象来建立这种通信。向后台线程传递一个IProgress<T>对象,并使用该对象以抽象的方式报告进度,UI线程接收这些进度通知并使用它们更新UI。可以在here中找到使用IProgress<T>接口的示例。这也是一个很好的读物:Async in 4.5: Enabling Progress and Cancellation in Async APIs

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68052346

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档