对于 WPF 的线程模型,Dispatcher 对象相信各位大伙伴已经不陌生,尤其是跨线程更新UI的时候,都会用它来调度消息。与 Dispatcher 对象有关的,还有一个叫 DispatcherFrame 的东东,开发文档是这么说的:Represents an execution loop in the Dispatcher,这样描述肯定是让人看不明白的,老周也不明白。

那咋办呢?根据老周十几年来积累下来的一些不要脸的经验,遇到这些很是抽象的玩意儿,可以有两种途径去了解:1、看.net 源代码,看看它能干吗;2、自己写几行代码试一试,许多时候,一试便能明了。

常看老周的垃圾博客的朋友一定很了解老周,老周向来重视示例,所以,下面咱们用示例来慢慢寻找真相,千万不要急,急的人都是浮躁。

好,首先,我们在窗口中放一个矩形,一个按钮。

    <Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Rectangle Height="" Fill="Red" Name="rect" Width=""/>
<Button Grid.Row="" Content="Click Me" Click="OnClick"/>
</Grid>

当按钮被单击后,会引发事件,然后,我在事件处理代码中,不断地让矩形的宽度增加,即增加 Width 属性。

我们先来第一种情形,把代码放到一个Task上,并用 Dispatcher 来更新矩形宽度,这种做法很常用。

            Task taskrun = new Task(() =>
{
double i = 0d;
for (i = 0d; i < 800d; i++)
{
Task.Delay().Wait();
Action act = () => rect.Width++;
Dispatcher.BeginInvoke(act, DispatcherPriority.Background);
}
});
taskrun.Start();

因为 Task 会在另一个线程上干活,所以修改UI要借助 Dispatcher 大叔来调度消息。DispatcherPriority.Background表示更新宽度的代码在后台进行。其实你可以更为这样:

Dispatcher.BeginInvoke(act, DispatcherPriority.Render);

这样优先级更高,优先照顾UI呈现,可以尽可能地及时更新矩形的宽度。

于是,你能看到这样的效果。

好,下面我们做第二轮试验,刚才的代码中,for 循环是在一个Task中执行的,所以它不占用UI线程,只是在修改矩形宽度时才会与UI线程交互。这次我们把所有代码都在UI线程上执行。

            Action invkAct = () =>
{
rect.Width++;
}; for (double i = 0d; i< 800d; i++)
{
Task.Delay().Wait();
Dispatcher.BeginInvoke(invkAct, DispatcherPriority.Render);
}

然后一运行,你就发现,卡住了。就算你把 DispatcherPriority 改为 Background 也一样卡住。因为for在UI线程上执行,UI更新的消息没有被及时处理(只有一条车道,塞车是肯定的),于是,更新矩形宽度的消息一直在消息队列中排队,只有等For 循环执行完了,才能更新矩形宽度,显然,这样不符合我们的预期了。

要么,你就像最前面的例子那样,把for循环写到Task上,以减轻UI线程的负担。如果你真的不想用新的线程去执行代码,而希望全在UI线程上执行,便真的要用上 DispatcherFrame 了。

DispatcherFrame 类有个很TMD重要的属性——Continue,为什么重要?先放着,待会儿回过头来再说。

要把一个 DispatcherFrame 插入到Dispatcher 中,就要调用 PushFrame 方法,这是个静态方法,Frame 会被插入到当前线程的 Dispatcher 对象中。在.net 源代码中,我发现 PushFrame 方法的实现代码,现贴出来,请各位与老周一同鉴赏。

       public static void PushFrame(DispatcherFrame frame)
{
…… Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
……
// 下面这个方法才是实现代码
dispatcher.PushFrameImpl(frame);
}
        private void PushFrameImpl(DispatcherFrame frame)
{
// 用来切换线程的上下文
SynchronizationContext oldSyncContext = null;
SynchronizationContext newSyncContext = null;
// Windows 消息
MSG msg = new MSG(); // _frameDepth 变量用来统计,代码执行前加1,代码执行后会减1
_frameDepth++;
try
{
// 临时更改线程上下文
oldSyncContext = SynchronizationContext.Current;
newSyncContext = new DispatcherSynchronizationContext(this);
SynchronizationContext.SetSynchronizationContext(newSyncContext); // 注意,下面代码是重点,开启一个消息循环
try
{
// 循环条件正是 Continue 属性
while(frame.Continue)
{
if (!GetMessage(ref msg, IntPtr.Zero, , ))
break; TranslateAndDispatchMessage(ref msg);
} // If this was the last frame to exit after a quit, we
// can now dispose the dispatcher.
if(_frameDepth == )
{
if(_hasShutdownStarted)
{
ShutdownImpl();
}
}
}
finally
{
// 还原线程上下文 SynchronizationContext.SetSynchronizationContext(oldSyncContext);
}
}
finally
{
_frameDepth--; // 注意这里减1
if(_frameDepth == )
{
// We have exited all frames.
_exitAllFrames = false;
}
}
}

代码看起来很长,看不懂不要紧,重点看那个 while 循环。

       while(frame.Continue)
{
if (!GetMessage(ref msg, IntPtr.Zero, , ))
break; TranslateAndDispatchMessage(ref msg);
}

现在,你明白 DispatcherFrame.Continue 属性的作用了吧。是的,如果它为真,那么这个循环会执行,GetMessage才会获取消息,并交由 TranslateAndDispatchMessage 函数进行处理。如果为假,那循环自然不会执行了。

由于 DispatcherFrame 会开启一个消息循环来提取未处理的消息,于是你一定想到了,在 for 的每一轮循环中向 Dispatcher 对象插入一个 DispatcherFrame 实例。但是,以下两种用法都无法及时更新UI。

方案A:无效。

            Dispatcher.PushFrame(new DispatcherFrame());
for (double n = 0d; n < 800d; n++)
{
Task.Delay().Wait();
rect.Width++;
}

方案B:依然无效。

            for (double n = 0d; n < 800d; n++)
{
Task.Delay().Wait();
rect.Width++;
ProcessMsgs();
} private void ProcessMsgs()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.PushFrame(frame);
frame.Continue = false;
}

以上两方案,一个是在循环之前开启一个新的循环,另一个则是在每一轮for循环中插入一个DispatcherFrame对象,并把Continue属性改为False,用以及时退出消息循环。

然而,想象很美好,实战很残酷。以上两个方案虽然让界面不卡了,但,矩形的宽度依旧不会实时更新。为什么会这样呢,根据老周的低见,应该是与优先级有关,毕竟 for 循环正在密集地执行,插入到消息队列的消息只能在厕所排长龙。

因为代码都在一个线程上执行,其实UI是做不到真正的实时更新的,但可以稍稍延迟更新,至少眼睛是看不出来的。

可以把代码改成这样:

        private void ProcessMsgs()
{
DispatcherFrame frame = new DispatcherFrame();
Action<object> cb = obj =>
{
DispatcherFrame f = obj as DispatcherFrame;
f.Continue = false;
};
Dispatcher.BeginInvoke(cb, DispatcherPriority.Background, frame);
Dispatcher.PushFrame(frame);
}

之所以要在BeginInvoke的委托中修改 Continue 属性,最重要的是可以设置优先级,除了Background,还可以使用 ApplicationIdle、SystemIdle等值,但不能用 Render、Send 这些优先级较高的值,因为这样也会被主消息循环阻塞,只能使用空闲或后台优先级。

这样改了之后,就可以达到预期效果了。

这类似于WinForm 中的 DoEvents 方法,MSDN上有这个示例。

总结一下,DispatcherFrame 的用途就是激活一新的消息循环,并以 Continue 属性作为退出新消息循环的标志。

示例源代码下载地址

【WPF】DispatcherFrame 是个啥玩意儿的更多相关文章

  1. 深入了解 WPF Dispatcher 的工作原理(PushFrame 部分)

    在上一篇文章 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分) 中我们发现 Dispatcher.Invoke 方法内部是靠 Dispatcher.Pu ...

  2. 【WPF】运用MEF实现窗口的动态扩展

    若干年前,老周写了几篇有关MEF的烂文,简单地说,MEF是一种动态扩展技术,比如可以指定以某个程序集或某个目录为搜索范围,应用程序在运行时会自动搜索符合条件的类型,并自动完成导入,这样做的好处是,主程 ...

  3. WPF上Arc Lisence的有关问题

    WPF下Arc Lisence的问题代码如下: using System; using System.Collections.Generic; using System.Configuration; ...

  4. WPF Dispatcher 一次小重构

    几个月之前因为项目需要,需要实现一个类似于WPF Dispatcher类的类,来实现一些线程的调度.之前因为一直做Asp.Net,根本没有钻到这个层次去,做的过程中,诸多不顺,重构了四五次,终于实现, ...

  5. WPF多线程演示

    WPF中的几种处理线程的工作方式: 1.简单的DispatcherTimer类似Timer控件 2.需要处理UI同步时,Dispatcher DispatcherOpertion 3.增强的Threa ...

  6. WPF学习(8)数据绑定

    说到数据绑定,其实这并不是一个新的玩意儿.了解asp.net的朋友都知道,在asp.net中已经用到了这个概念,例如Repeater等的数据绑定.那么,在WPF中的数据绑定相比较传统的asp.net中 ...

  7. WPF刷新界面之坎坷路

    WPF刷新界面之坎坷路 项目需要一个硬件检测功能,需要用到界面刷新,刚开始想用个定时器,对检测过的硬设定时添加后刷新界面. 但是很遗憾,定时器并不能进行刷新.后台检测List数据里面已经添加了很多了很 ...

  8. winform/wpf 程序部署

    (1):一些发布方式 ClickOnce是什么玩意儿,这个问题嘛,在21世纪的互联网严重发达的时代,估计也没有必要大费奏章去介绍了,弄不好的话,还有抄袭之嫌.因此,有关ClickOnce的介绍,各位朋 ...

  9. 【WPF】在新线程上打开窗口

    当WPF应用程序运行时,默认会创建一个UI主线程(因为至少需要一个),并在该UI线程上启动消息循环.直到消息循环结束,应用程序就随即退出.那么,问题就来了,能不能创建新线程,然后在新线程上打开一个新窗 ...

随机推荐

  1. Linux下Hadoop2.7.1集群环境的搭建(超详细版)

                                本文旨在提供最基本的,可以用于在生产环境进行Hadoop.HDFS分布式环境的搭建,对自己是个总结和整理,也能方便新人学习使用. 一.基础环境 ...

  2. php函数的种类与调用方法大揭密

    PHP中的函数看上去很简单,实际上功能非常强大,我这里按函数名称是否固定,可以分为以下三大类: 一.名称固定的函数: 这类函数,也叫:常规函数,直接用关键字function来创建,也是大家最熟悉的类型 ...

  3. 博弈论(Game Theory) - 02 - 前传之重复剔除严格劣战略的占优战略均衡

    博弈论(Game Theory) - 02 - 前传之重复剔除严格劣战略的占优战略均衡 开始 "重复剔除劣战略的严格占优战略均衡"(iterated dominance equil ...

  4. 读 Zepto 源码之神奇的 $

    经过前面三章的铺垫,这篇终于写到了戏肉.在用 zepto 时,肯定离不开这个神奇的 $ 符号,这篇文章将会看看 zepto 是如何实现 $ 的. 读Zepto源码系列文章已经放到了github上,欢迎 ...

  5. Java IO详解(一)------File 类

    File 类:文件和目录路径名的抽象表示. 注意:File 类只能操作文件的属性,文件的内容是不能操作的. 1.File 类的字段 我们知道,各个平台之间的路径分隔符是不一样的. ①.对于UNIX平台 ...

  6. Java_中快速获取系统时间

    直接调用System的currentTimeMillis()即可! long start = System.currentTimeMillis(); System.out.println(" ...

  7. SpringBoot系列(一)RestTemplate

    作为springBoot的开篇系列,RestTemplate只能表示我只是个意外 what RestTemplate是spring提供的用于访问rest服务的客户端(其实类似Apache的HttpCl ...

  8. python 计算两个日期相差多少个月

    近期,由于业务需要计算两个日期之前相差多少个月.我在网上找了很久,结果发现万能的python,居然没有一个模块计算两个日期的月数,像Java.C#之类的高级语言,都会有(date1-date2).mo ...

  9. javaWeb学习总结(10)- Filter(过滤器)常见应用(3)

    一.统一全站字符编码 通过配置参数charset指明使用何种字符编码,以处理Html Form请求参数的中文问题 package me.gacl.web.filter; import java.io. ...

  10. 模仿ICE的structured panorama小按钮

    这个按钮的目的是用于手动排列图片序列,应该说写得比较精巧,我使用csharp进行模仿,主要采用的是自动控件创建技术.结果比较简陋,实现功能而已,放出来大家一起学习. ;        ;        ...