国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
WPF線程模型及委托事件應(yīng)用

參考:http://www.cnblogs.com/zhouyinhui/archive/2008/01/27/1055261.html

第一次做WPF項(xiàng)目,要用到多線程,委托,自定義事件等技術(shù)??戳藥灼W(wǎng)友技術(shù)文章覺得不錯(cuò),做下筆記。


在多線程編程中,我們經(jīng)常會(huì)需要在子線程中訪問主線程的內(nèi)容,特別是更改主窗體的UI界面內(nèi)容,如果直接訪問跨線程資源,系統(tǒng)就會(huì)報(bào)線程訪問錯(cuò)誤.查了MSDN發(fā)現(xiàn)由于這樣的做法是線程不安全的。在WinForm中窗體類提供了Invoke方法以從子線程中訪問主線程資源.在WPF中Window類并沒有Invoke方法,但是WPF提供了專門負(fù)責(zé)線程調(diào)度工作的Window.Dispatcher類,每個(gè)線程都有一個(gè),我們在一個(gè)線程中要讓另一個(gè)線程做事情,其實(shí)就是調(diào)用目標(biāo)Dispatcher調(diào)度完成.

Dispatcher

WPF規(guī)定了(事實(shí)上在.net2.0中便已規(guī)定了)UI元素只能由創(chuàng)建該元素的線程來訪問。比如我們從新開的一個(gè)線程中訪問主界面中的元素會(huì)出現(xiàn)運(yùn)行時(shí)的異常。Dispatcher來維持著這一規(guī)定,并組織著消息循環(huán)。Dispatcher負(fù)責(zé)檢測訪問對象的線程與對象創(chuàng)建線程是否一致,不一致則拋出異常。值得一提的是,Dispatcher的消息循環(huán)中的Work Item是有優(yōu)先級的,這可以讓高優(yōu)先級的項(xiàng)能有更多的工作時(shí)間。比如界面繪制比處理用戶輸入的優(yōu)先級要高,這使得界面動(dòng)畫更加流暢。這也就是為什么,我們在調(diào)用Dispatcher.Invoke ( DispatcherPriority,…) 與Dispatcher. BeginInvoke (DispatcherPriority,…)要傳入一個(gè)優(yōu)先級參數(shù)的原因。下面是對各個(gè)優(yōu)先級的說明:

優(yōu)先級

說明

Inactive

工作項(xiàng)目已排隊(duì)但未處理。

SystemIdle

僅當(dāng)系統(tǒng)空閑時(shí)才將工作項(xiàng)目調(diào)度到 UI 線程。這是實(shí)際得到處理的項(xiàng)目的最低優(yōu)先級。

ApplicationIdle

僅當(dāng)應(yīng)用程序本身空閑時(shí)才將工作項(xiàng)目調(diào)度到 UI 線程。

ContextIdle

僅在優(yōu)先級更高的工作項(xiàng)目得到處理后才將工作項(xiàng)目調(diào)度到 UI 線程。

Background

在所有布局、呈現(xiàn)和輸入項(xiàng)目都得到處理后才將工作項(xiàng)目調(diào)度到 UI 線程。

Input

以與用戶輸入相同的優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。

Loaded

在所有布局和呈現(xiàn)都完成后才將工作項(xiàng)目調(diào)度到 UI 線程。

Render

以與呈現(xiàn)引擎相同的優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。

DataBind

以與數(shù)據(jù)綁定相同的優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。

Normal

以正常優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。這是調(diào)度大多數(shù)應(yīng)用程序工作項(xiàng)目時(shí)的優(yōu)先級。

Send

以最高優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。

上面提到了Dispatcher維持著一個(gè)規(guī)矩“只有創(chuàng)建該對象的線程可以訪問該對象”。這里的對象不僅僅是指一些UI控件(比如Button),而是所以的派生于DispatcherObject類的對象。

對于阻塞的操作,不一定需要開啟新線程

當(dāng)我們遇到某個(gè)費(fèi)時(shí)的操作是,第一反映往往是開啟一個(gè)新線程,然后在后臺(tái)去處理它,以便不阻塞我們的用戶界面。當(dāng)然,這是正確的想法。當(dāng)并不是所有的都需如此。仔細(xì)想想界面阻塞的原因,我們知道其是由于時(shí)間被消耗在某個(gè)費(fèi)時(shí)的Work Item上了,而那些處理用戶界面的Work Item還在那里苦苦等候。So,我們只要?jiǎng)e讓他們苦苦等候就可以了,只要用戶有界面操作我們就處理,線程上的其他空閑時(shí)間來處理我們的復(fù)雜操作。我們將復(fù)雜的費(fèi)時(shí)的操作細(xì)化成很多不費(fèi)時(shí)的小操作,在這些小操作之間的空隙處我們來處理相應(yīng)用戶界面的操作

阻塞的情況如下, MouseUp與MouseLeave會(huì)被阻塞:

(MouseDown)->(費(fèi)時(shí)的,復(fù)雜操作)->(MouseUp)->(MouseLeave)…

細(xì)化后的情況如下,MouseUp與MouseLeave不會(huì)被阻塞:

(MouseDown)->(不費(fèi)時(shí)的,小操作,復(fù)雜操作的1/n)->(MouseUp)->(不費(fèi)時(shí)的,小操作,復(fù)雜操作的1/n) -> (MouseLeave)…

舉一個(gè)簡單的例子,假定我們的主界面上要顯示一個(gè)數(shù)字,其為Window1的CurrentNum屬性,我們已經(jīng)將界面上的某個(gè)TextBlock與其綁定了起來:

            <TextBlock x:Name="textBlock_ShowNum"
                       Text="{Binding ElementName=window1,Path=CurrentNum}"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Center" />

當(dāng)我們點(diǎn)擊界面上的一個(gè)按鈕后,要求該數(shù)字被不停的累加,直到再次點(diǎn)擊該按鈕是停止.實(shí)際效果相當(dāng)于:

            while (this.IsCalculating)
            {
                this.CurrentNum++;
            }

如果我們直接按照上面的While語句來書寫程序,明顯,當(dāng)用戶點(diǎn)擊按鈕后,整個(gè)線程將在這里被堵死,界面得不到更新,用戶也沒有辦法再次點(diǎn)擊按鈕來停止這個(gè)循環(huán),遭透了。

既不開啟新線程又不阻塞界面應(yīng)該怎么辦呢?

我們知道this.CurrentNum++;語句以及更新綁定到CurrentNum屬性的TextBlock并不耗費(fèi)時(shí)間的,耗費(fèi)時(shí)間的是他們的累加而成的死循環(huán),所以,我們將這個(gè)循環(huán)分解成無數(shù)個(gè)僅僅由this.Current++語句組成的小方法,并在這些小方法的之間來處理用戶界面:

public delegate void NormalDelegate();

        void button_StartOrStop_Click(object sender, RoutedEventArgs e)
        {
            if (this.IsCalculating)
            {
                NormalDelegate calNumDelegate = new NormalDelegate(this.CalNum);
                this.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, calNumDelegate);
            }
        }

        private void CalNum()
        {
            this.CurrentNum++;

            if (this.IsCalculating)
            {
                NormalDelegate calNumDelegate = new NormalDelegate(this.CalNum);
                this.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, calNumDelegate);
            }
        }

上面的兩段代碼可以簡單地如下示意:

阻塞的情況如下, MouseUp與MouseLeave會(huì)被阻塞:

(MouseDown)->(費(fèi)時(shí)的While(true))->(MouseUp)->(MouseLeave)…

細(xì)化后的情況如下,MouseUp與MouseLeave不會(huì)被阻塞:

(MouseDown)->(不費(fèi)時(shí)的CalNum)->(MouseUp)->(不費(fèi)時(shí)的CalNum) -> (MouseLeave)…

 

用Delegate.Invoke()或Delegate.BeginInvoke()來開啟新線程

除了new 一個(gè)Thread對象外,使用Delegate的Invoke或BeginInvoke方法也可以開啟新的線程。

假設(shè)有下面這一個(gè)很費(fèi)時(shí)的方法,我們應(yīng)該如何使用Delegate來改造呢

 

        private void TheHugeMethod()
        {
            Thread.Sleep(2000);
            
            this.button_Test.Content = "OK!!!";
        }

首先,我們聲明一個(gè)可以用于TheHugeMethod方法的代理:

public delegate void NormalMethod();

然后對TheHugeMethod構(gòu)造一個(gè)NormalMethod類型的對象,并調(diào)用其Invoke方法(同步調(diào)用)或BeginInvoke方法(異步調(diào)用)

        void button_Test_Click(object sender, RoutedEventArgs e)
        {
            NormalMethod hugeMethodDelegate = new NormalMethod(this.TheHugeMethod);
            hugeMethodDelegate.BeginInvoke(null, null);
        }

由于是開啟了新的線程,所以TheHugeMethod方法中對this.button_Test控件的調(diào)用語句也得改造一下:

 

 

        private void TheHugeMethod()
        {
            Thread.Sleep(2000);
            
            //will crash
            //this.button_Test.Content = "OK!!!";

            NormalMethod updateUIDelegate = new NormalMethod(this.UpdateUI);
            this.button_Test.Dispatcher.BeginInvoke(DispatcherPriority.Normal, updateUIDelegate);
        }

        private void UpdateUI()
        {
            this.button_Test.Content = "OK!!! ";
        }

在新線程中執(zhí)行消息循環(huán)

一般情況下我們不需要在新線程中執(zhí)行消息循環(huán)了,因?yàn)槲覀兂3J窃谛戮€程中執(zhí)行一些后臺(tái)操作而不需要用戶在新線程中執(zhí)行UI操作(比如我們在新線程中從網(wǎng)絡(luò)上下載一些數(shù)據(jù)然后UI線程來顯示這些數(shù)據(jù))。當(dāng)有時(shí)新線程卻是需要消息循環(huán)的,最簡單的例子是操作系統(tǒng)的“資源管理器”,每一個(gè)資源管理器窗口都在一個(gè)單獨(dú)的線程中(它們都在同一個(gè)進(jìn)程中)。

但當(dāng)你按照如下方式編寫代碼來新建一個(gè)資源管理器窗口時(shí),會(huì)出問題:

 

        private void button_NewWindow_Click(object sender, RoutedEventArgs e)
        {
            Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
            newWindowThread.SetApartmentState(ApartmentState.STA);
            newWindowThread.IsBackground = true;
            newWindowThread.Start();
        }

        private void ThreadStartingPoint()
        {
            Window1 newWindow = new Window1();
            newWindow.Show();
        }

問題是newWindow閃現(xiàn)一下就消失了。因?yàn)樵撔麓翱跊]有進(jìn)入消息循環(huán),當(dāng)newWindow.Show()方法執(zhí)行完畢后,新線程的一切都結(jié)束了。

正確的方法是在newWindow.Show();方法后加入Dispatcher.Run()語句,其會(huì)將主執(zhí)行幀推入該Dispatcher的消息循環(huán)中。

        private void ThreadStartingPoint()
        {
            Window1 newWindow = new Window1();
            newWindow.Show();
            System.Windows.Threading.Dispatcher.Run();
        }

BackgroundWorker實(shí)質(zhì)是:基于事件的異步模式

在多線程編程中,最爽的莫過于.net 提供了BackgroundWorker類了。其可以:
“在后臺(tái)”執(zhí)行耗時(shí)任務(wù)(例如下載和數(shù)據(jù)庫操作),但不會(huì)中斷您的應(yīng)用程序。
同時(shí)執(zhí)行多個(gè)操作,每個(gè)操作完成時(shí)都會(huì)接到通知。
等待資源變得可用,但不會(huì)停止(“掛起”)您的應(yīng)用程序。
使用熟悉的事件和委托模型與掛起的異步操作通信。

我想大家對BackgroundWorker亦是再熟悉不過了,這里就不多做介紹了,另外“基于事件的異步模式”是WinForm的內(nèi)容,但在WPF中完美運(yùn)用(原因是WPF用DispatcherSynchronizationContext擴(kuò)展了SynchronizationContext),可以參見MSDN“Event-based Asynchronous Pattern Overview”



 

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
WPF的線程模型
詳解WPF線程模型和Dispatcher
WPF - 在子線程中顯示窗口
WPF 基礎(chǔ)到企業(yè)應(yīng)用系列3——Dispatcher及多線程
(轉(zhuǎn))Silverlight多線程能力(上)
C# this.Invoke()的作用與用法
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服