深入理解WPF框架下await的实践

前言:

这一段时间开始在着手WPF的项目,在开发过程的间歇恶补下WPF基础。asyc await作为framework4.5的新特性,也在我的项目中得到应用。有个这个特性以后确实又是一个大大的语法糖福利,程序代码漂亮简洁多。大致的执行顺序也可以从院子的一篇「async & await的前世今生」得知,微软msdn的例子也是简洁明了,但是总有一种“知其然而不知所以然”的感觉,萦绕在心头很是难受。微软给我们封装得太好了,让我们即使“不求甚解”的情况下也可以玩得转。就像是骇客帝国,不甘心与再在这个“盒子”中安逸了,至少开一扇天窗来一探究竟。

正文:

既然这段时间一直在补WPF的基础,那么对await的研究也打算从这个框架入手。

首先先写一个最最简单的WPFDemo来作为研究对象。

<Window x:Class="WPFResearchDispatcher.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <Rectangle x:Name="rectangle" Height="200" ></Rectangle>
            <Button x:Name="btnTest" Click="btnTest_Click" Height="100">Click</Button>
        </StackPanel>
    </Grid>
</Window>

xmal上无非就是一个矩形和一个测试按钮

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 using System.Windows;
 8 using System.Windows.Controls;
 9 using System.Windows.Data;
10 using System.Windows.Documents;
11 using System.Windows.Input;
12 using System.Windows.Media;
13 using System.Windows.Media.Imaging;
14 using System.Windows.Navigation;
15 using System.Windows.Shapes;
16 using System.Windows.Threading;
17
18 namespace WPFResearchDispatcher
19 {
20     /// <summary>
21     /// MainWindow.xaml 的交互逻辑
22     /// </summary>
23     public partial class MainWindow : Window
24     {
25         public MainWindow()
26         {
27             InitializeComponent();
28         }
29
30         private async void btnTest_Click(object sender, RoutedEventArgs e)
31         {
32             var task = Task.Delay(2000);
33             await task;
34
35             this.rectangle.Fill = Brushes.Red;
36
37         }
38     }
39 }

这是一个普通的不能再普通的await的Sample,点击按钮后2秒,矩形填充色变成红色。UI不会卡顿。of course,it works.

接下来,我们分别在btnTest_Click,开始时,和await之后分别加入断点,看看发生了什么。

点击Click按钮

断点命中,让我们再来看看调用堆栈

这时候大家可能觉得很奇怪,说到这堆栈还和await没半毛钱的关系。大家先不要急,让我先慢慢说完再慢慢细细回味。

这个调用堆栈很明确勾勒出了点击事件的链路。

WPF的底层还是以Windows消息队列来实现的。

开启程序后,主程序的Dispatcher, PushFrame开启消息泵。

鼠标外设点击后,在操作系统层面以WindowsAPI的消息队列发出一个消息。

WPF获取此消息后,InputManger找到这个区域的相关控件来RaiseEvent路由事件,最后到达我们的btnTest_Click方法。

对于这一过程的消息过程,周永恒的一站式WPF--Window(一)里面有很深入的介绍,这这里就不搬书了。

这个Windows消息的值,通过调试窗口,我们可以获得是514,先记下来以后有用。

继续F5,等了2秒中后继续命中断点,这个断点是在await以后了

让我们再来看调用堆栈

我们发现这次调用堆栈比前面要短了很多。

我们发现也是从SubclassWndProc接收Windows消息队列开始的。中间的InputManger的处理,路由事件什么的统统不见了,也就是说,除了执行的方法体是在btnTest_Click里面,其实这段程序的执行和前面的Click事件完全没有任何关系。

为了进一步证实推断,我再次查看这个windows消息的id,为49563,和前面的完全是两个消息。是Dispatcher分别处理的。

调试到这一步,冒出了更多的疑问,那么这个windows消息的id 49563是哪里来的拿。第一次消息是操作系统发的。那么第二次消息是哪个家伙“偷偷”发了,而我们还不知道。

进一步跟进代码,到Dispatcher类里面去找些蛛丝马迹。

我的办法比较笨和死板,一层层的看下去。终于在     WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) 行 235    C#
这个调用堆栈找到线索

private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            WindowMessage message = (WindowMessage) msg;
            if (this._disableProcessingCount > 0)
            {
                throw new InvalidOperationException(MS.Internal.WindowsBase.SR.Get("DispatcherProcessingDisabledButStillPumping"));
            }
            if (message == WindowMessage.WM_DESTROY)
            {
                if (!this._hasShutdownStarted && !this._hasShutdownFinished)
                {
                    this.ShutdownImpl();
                }
            }
            else if (message == _msgProcessQueue)
            {
                this.ProcessQueue();
            }

这个函数全部代码较长,为了突出重点,我这里就只取前面部分,通过堆栈我们可以知道下个调用函数是ProcessQueue()

通过调试窗口,我看到 _msgProcessQueue正好是49563!

好了再回来看_msgProcessQueue是什么

  [SecurityCritical]
        private static WindowMessage _msgProcessQueue = MS.Win32.UnsafeNativeMethods.RegisterWindowMessage("DispatcherProcessQueue");

这是定义在Dispatcher类中的静态自定义注册消息

那么有出必然是有进啊,那这消息是谁“推”进来的

我再看看_msgProcessQueue有什么引用,是private的找起来很方便,在整个Dispatcher

再找到RequestProcessing

再找到InvokeAsyncImpl

LegacyBeginInvokeImpl

越来越近了。。。。

兜兜转转绕一圈,最后发现是BeginInvoke。欧!好像有点感觉了!

正好msdn有篇文章Await, SynchronizationContext, and Console Apps

里面论述了Await是因为SynchronizationContext的关系,SynchronizationContext是抽象类。我们可以很容易获知WPF的SynchronizationContext的实现是DispatcherSynchronizationContext。

摘录原文如下,作者用代码示意了await的实现。

await FooAsync();

RestOfMethod();

as being similar in nature to this:

var t = FooAsync();

var currentContext = SynchronizationContext.Current;

t.ContinueWith(delegate

{

    if (currentContext == null)

        RestOfMethod();

    else

        currentContext.Post(delegate { RestOfMethod(); }, null);

}, TaskScheduler.Current);

实际上是在完成异步方法后“偷偷”调用了 SynchronizationContext的Post的方法。

趁热打铁,马上杀回到DispatcherSynchronizationContext里面去把这代码扣出来

        public override void Post(SendOrPostCallback d, object state)
        {
            this._dispatcher.BeginInvoke(this._priority, d, state);
        }

代码无比的简洁,和调试代码的结论一致,这个“环”终于开始圆起来了。

好,最后为了证实自己的推论,最后再做一次验证。

在WindowsBase.dll引用下Enable Debugging

在Post方法上加入断点。

再次运行调试

OK,命中,和预期一致,这个断点在我前面设的两个断点中间触发了

再来看看调用堆栈

OK,偷偷摸摸做的事情总算完全曝光在我们眼前了,堆栈的内容也很容易理解。

内部时钟在检测到异步任务完成后开始了PostAcition,把后续要做的事情包装成一个代理,通过BeginInvoke,压到消息泵里。

接下来就是我们第三个断点跟到的,Dispatcher又从队列里拿到了这个message,再开始做await后面的事情……

总结:

await的实现完美的契合在WPF的Dispatcher体系下运转。现在一切一切的解释都显得合理了。

WPF在处理await时,执行到await以后就直接返回了。从Dispatcher这层来讲,方法体执行到这里已经吧这个“消息”下要处理的事情干完了。

这里就可以解释,我们的UI没有任何的死锁。

具体的asyc方法管他自己执行,和消息队列暂时没有任何交集。

asyc方法执行完成后,再获得当前的SynchronizationContext去Post消息,“申请”执行剩余的方法

Dispatcher收到队列消息,执行剩余的方法。

举一个生活化的例子:

小明去ATM机存钱,但是到ATM才刚刚反应过来小明的钱在小李那里,小李要半小时才能赶过来给他。

怎么办了,如果占着ATM的队伍等钱拿来,那只能占着茅坑让其他人埋怨(锁死UI线程)。比较好的办法是小明到了银行后先去干点其他事情(完成返回),让小李早点送钱来(asyc)。小李把钱送来了,小明可以去存钱了,当然他不能直接去存,如果有人在排队的话,他必须开始排队(SynchronizationContext.Post)。小明终于再次等到开始存钱(MessageProc

其他一些相关的问题:

如果await不使用的化,可以改成await Task.Delay(2000).ConfigureAwait(false);

如果用调试看,堆栈非常简单,是直接线程池的默认实现。和Dispatcher没有任何关系,当然接下来操作UI元素也会报错。。。。

这一段尝试是 await之后的线程问题这篇Blog得到启发。

写在最后

在相关技术资料(Blog)和强悍工具(Refector)的帮助下,总算搞懂了一些一直困扰自己的问题。希望能对大家有所帮助。我的基础实力还是比较浅的,对于Windows编程只有大学时代学的WindowsAPI。对于MFC和C++一些底层都没有好好接触过。如有错误之处欢迎大家批判指正。

相关链接

周永恒Blog

async & await 的前世今生

Await, SynchronizationContext, and Console Apps

时间: 2024-10-06 02:22:14

深入理解WPF框架下await的实践的相关文章

WPF Prism框架下基于MVVM模式的命令、绑定、事件

原文:WPF Prism框架下基于MVVM模式的命令.绑定.事件 Prism框架下的自定义路由事件和命令绑定 BaseCode XAML代码: <Button x:Class="IM.UI.CommandEx.PrismCommandEx" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/wi

【我们一起写框架】MVVM的WPF框架之绑定(二)

MVVM的特点之一是实现数据同步,即,前台页面修改了数据,后台的数据会同步更新. 上一篇我们已经一起编写了框架的基础结构,并且实现了ViewModel反向控制Xaml窗体. 那么现在就要开始实现数据同步了. DataContext—数据上下文 在实现数据同步前,我们要了解一个知识点——DataContext. WPF中每个UI都有一个Content和一个DataContext,那么Content和DataContext是什么呢? Content:Content是指页面内容,即我们编写的代码,或者

.NET Core 多框架支持(net45+netstandard20)实践中遇到的一些问题总结

.NET Core 多框架支持(net45+netstandard20)实践中遇到的一些问题总结 前言 本文主要是关于.NET Standard 代码 在多框架 和 多平台 支持自己实践过程中遇到的一些问题和解决办法,希望给遇到这些问题的同学一点参考和思路.问题基本上都是提在 博问 和 Stackoverflow 中,不乏很多大佬都提供了解决问题的思路.接下来则是正文. 问题1:如何发布多个 TargetFramework 的nuget 包 问题来源 :主要是因为之前的类库只提供了 .NET S

spring框架下配置lucene

lucene特点及效果文章里就不说了,网上有的是.我就简单说下自己号码大全了解, 正常 sql 查询时:name like '%继中%' 想必咱们一定理解这样不会走索引关键词挖掘的,然后就在多行数据等级查询相应时刻会很慢,对吧,由于数据库在一行行扫呢.所以咱们自然会想到怎样能让它走索引? 解决方案之一:lucene出来了. 本来它即是帮你把文章拆分红若干个关键字,这样以便按关键字查询时能经过关键字直接查询来确定哪些文章匹配该关键字并疾速回来.说再直白点,即是 sql句子的查询不必like ,而是

在Spring Boot框架下使用WebSocket实现消息推送

Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的支持(使用Spring Boot开发Web项目(二)之添加HTTPS支持),在这两篇文章的基础上,我们今天来看看如何在Spring Boot中使用WebSocket. 什么是WebSocket WebSocket为浏览器和服务器之间提供了双工异步通信功能,也就是说我们可以利用浏览器给服务器发送消息,

MySQL在Django框架下的基本操作(MySQL在Linux下配置)

[原]本文根据实际操作主要介绍了Django框架下MySQL的一些常用操作,核心内容如下: ------------------------------------------------------------------------------------------------- 1. Linux环境下MySQL的安装与配置 2. [Linux]MySQL在Django框架下的基本操作 3. 本文相关的一些参考网址 注:本文会根据实践,持续更新文档,如有错误,希望读者指出哈!~ -----

正确理解WPF中的TemplatedParent

(注:Logical Tree中文称为逻辑树,Visual Tree中文称为可视化树或者视觉树,由于名称不是很统一,文中统一用英文名称代表两个概念,况且VisualTreeHelper和LogicalTreeHelper也是WPF中提供的类名称) 众所周知WPF中的Logical Tree是逻辑上定义的元素层次树,而实际上显示在屏幕上的元素层次树是Visual Tree,Visual Tree是 (注:Logical Tree中文称为逻辑树,Visual Tree中文称为可视化树或者视觉树,由于

【工作笔记二】ASP.NET MVC框架下使用MVVM模式

ASP.NET MVC框架下使用MVVM模式 原文:http://www.cnblogs.com/n-pei/archive/2011/07/21/2113022.html 对于asp.net mvc开发人员或爱好者来说,MVVM模式可能你不是很了解.本来这个模式就是针对WPF和Silverlight开发提出的开发模式. 大概一年前,我当时迷恋Silverlight时,学习了MVVM一段时间,没想到现在可以在MVC用到. 我看了下之前有两篇文章介绍MVVM的.希望可以对MVVM不了解的人有点帮助

【WPF学习】第五章 理解WPF的布局

在Windows开发人员设计用户界面的方式上,WPF布局模型是一个重大改进.在WPF问世之前,Windows开发人员使用刻板的基于坐标的布局将控件放到正确位置.在WPF中,这种方式虽然可行,但已经极少使用.大多数应用程序将使用类似Web的流(flow)布局:在使用流布局模型时,控件可以扩大,并将其他控件挤到其他位置.开发人员能创建与现实分辨率和窗口大小无关的.在不同的显示器上正确缩放的用户界面:当窗口内容发生变化时,界面可调整自身,并且可以自如地处理语言的切换.要利用该系统的优势,首先需要进一步