让那些做面试官的屌丝lead不再抖脚系列(五)--- UI线程到底是什么

因为之前一直是做C/S这块,例如wpf这种已经更新的好玩的东西,因此好多面试官都会问,而且很多地方也都需要用到,

你知道wpf的异步实现么,或者你知道UI线程怎么样才能被调用么?这些问题看起来很好准备,其是有老大学问了,之前看到个帖子:

http://www.cnblogs.com/Zhouyongh/archive/2011/01/12/1933414.html


这个写的非常非常详细了,但是很多太学术,很难懂。


通常,WPF 应用程序从两个线程开始:一个用于处理呈现,一个用于管理 UI。呈现线程有效地隐藏在后台运行,而 UI 线程则接收输入、处理事件、绘制屏幕以及运行应用程序代码。

UI 线程对一个名为 Dispatcher 的对象内的工作项进行排队。 Dispatcher 基于优先级选择工作项,并运行每一个工作项,直到完成。每个 UI 线程都必须至少有一个 Dispatcher,并且每个 Dispatcher 都只能在一个线程中执行工作项。


国际惯例,我们还是拿msdn的介绍来做开头,这个很好理解,wpf初始有2个线程,一个管你的ui长什么样,一个管后台数据或者计算什么的,而且这两个线程相互独立,不能用简单的方式相互干涉,于是有了第一个问题,后台线程想访问ui线程,要怎么做呢?

默认所有在xaml.cs里做的事情都是在ui线程中做的,因此想造个多线程,就要用我们之前用到的task咯thread里随便选一个来开个我们自己的线程:

        private void AsyncButton_Click(object sender, RoutedEventArgs e)
        {

            Task.Factory.StartNew(() =>
            {

                while (true)
                {
                    Thread.Sleep(1000);
                    this.testText.Text = DateTime.Now.ToLongTimeString();
                }

            });

很简单的例子,button点击后我希望textBlock开始记录当前时间,其是也就是个简单的时钟功能,这样就出现了Task的新线程想访问UI线程,因为textBlock是在ui线程中的,然后这样肯定是会爆炸的,因为UI线程不允许别的线程来访问,因此有了Dispatcher这个东西,当我们把代码弄成这样:

                while (true)
                {
                    Thread.Sleep(1000);
                    this.Dispatcher.BeginInvoke(new AsyncDelegate(() =>
                    {
                        this.testText.Text = DateTime.Now.ToLongTimeString();
                    }));
                }

AsyncDelegate是个自定义代理,可以无视,你会发现这样就成功了,并且UI线程是可以使用的,比如点别的button。

这里特地我们用了dispatcher.BeginInvoke,还有个invoke,到底有神马差别呢?大神说过,invoke其是就是beginInvoke,他内部调用了beginInvoke:

当我们调用Invoke,对传入的参数DispatcherPriority进行判断,如果是Send,这是个特殊的优先级,直接切换线程上下文,执行任务并返回。如果是其他的优先级,调用BeginInvoke。

大神说的其是就是这样,系统会将我们传给Dispatcher的东西包装成DispatcherOperation去WndProc中干该干的事,win32不是很熟,所以只能照搬了~

但是大神也是人,并不能面面俱到,所以我们的问题还在,我们之前代码中的调用时异步咯,所以我们可以操作UI上做些别的事情,那其实很简单,我们把Thread.sleep移到invoke里面呢,会怎么样?

                while (true)
                {
                    
                    this.Dispatcher.BeginInvoke(new AsyncDelegate(() =>
                    {
                        Thread.Sleep(1000);
                        this.testText.Text = DateTime.Now.ToLongTimeString();
                    }));
                }

这样的结果很可能会被认为和刚才一样,说实话,我去面试别人,我肯定不会问那些翻字典翻百科才能背出来的东西,就问问这两个的结果是否一样,基本就能明白了。

其是结果是不一样的,后面这样之后会造成程序假死,其实就是无响应的喜闻乐见结果。这是为什么呢?

那我们先说结果,结果其是就是其是UI线程一直在被this.text = xxx这个方法占据,我们没法干别的了,所以结果就是,其是beginInvoke也是占用UI线程干事情,并不是我们所谓的在别的线程上做事情,然后UI线程可供我们用,UI线程只有一个,因此beginInvoke只是在当前线程上异步做事情,就像你有3个方法一起执行beginInvoke*3 ,结果其是就是帮你把这些事情一起在UI线程上做事,顺序不定,只是帮你排进队列,不是马上执行,而invoke是马上执行,而排队的任务就是dispatcher干的。

我们模拟个场景,或许你就能恍然大悟,假设现在有两个人,一个是小兵,一个是司令,我们把小兵当成后台线程,司令当成UI线程,所以第一段代码的场景会变成,小兵去杀10个敌人,然后杀完后告诉司令,我杀完了,司令说了句哦,然后小兵继续回去杀10个...,而第二段是,小兵上来就冲到司令面前,司令你看着我,我杀10个人给你看,然后小兵杀了10个,司令说哦,然后小兵回到自己位置上,瞬间又跑回司令面前,司令你再看我杀10个....这样其实小兵一直霸占着司令的行动权,导致别的士兵无法请司令帮忙或者别的事情,而小兵离开回来的时间太短我们可以忽略,就是while(true)的位置,而第一段中,司令说哦的时间很短,我们也可以忽略,所以UI线程接近空闲,记住,是接近空闲,如果你的机器够破,连跑个最简单的1+1=2都会卡,你就会发现你的程序时不时在未响应。

因此control.BeginInvoke和普通的我们之前说的delegate.BeginInvoke是不一样的,你可以说他是异步,但是他真的不是多线程,异步和多线程的不同,你也可以用这个例子来解释。


而我们熟知的DispatcherTimer,其是也就是通过了beginInvoke,类似于我们上面的方法进行实现的,而由于他是亲儿子,一般涉及到时间的功能,我们都会通过他来进行实现。


其是UI线程是个很懒的线程,他不愿意去做很多事情,当然你可以强迫他做,他的反应就是直接反应在你脸上,死机,无响应,这些就是后果,所以一般我们把大量的计算都写到了后台线程,而UI线程只是做传值,显示值等操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值