Office中國(guó)論壇/Access中國(guó)論壇

 找回密碼
 注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

tag 標(biāo)簽: 多線程

相關(guān)帖子

版塊 作者 回復(fù)/查看 最后發(fā)表

沒(méi)有相關(guān)內(nèi)容

相關(guān)日志

分享 Access菜鳥(niǎo)七大邪門(mén)武器之五:在access中使用多線程(三)
熱度 1 ganlinlao 2016-4-3 10:44
Access菜鳥(niǎo)七大邪門(mén)武器之五:在access中使用多線程(三)
在這里我將萬(wàn)分感謝網(wǎng)友西門(mén)吹雪。如果不是他的辛苦和努力,我們很難想到在Access中用上多線程,至少在此之前,我從沒(méi)想過(guò)!皠e人的一小步,我們節(jié)省很長(zhǎng)的一段路”。西門(mén)吹雪,謝謝你。 VB/COM 多線程概述 多線程程序 Visual Basic和COM Thread Factory 異步調(diào)用 錯(cuò)誤處理 調(diào)試 封裝傳送 ActiveX EXE服務(wù)器模型 同步和線程安全 TLS (Thread Local Storage,線程本地存儲(chǔ))     這篇指南提供了一篇關(guān)于使用Visual Basic和Thread Factory開(kāi)發(fā)多線程COM應(yīng)用程序的概述。它假設(shè)讀者對(duì)COM和COM線程模型(包括COM STA )已經(jīng)有一定的了解。其中不會(huì)包含MTA(Multi Threaded-Apartment,多線程套間),主要是因?yàn)樵赩isual Basic不能創(chuàng)建MTA對(duì)象。   一個(gè) 多線程程序 是一個(gè)可以在同一個(gè)應(yīng)用程序當(dāng)中同時(shí)執(zhí)行2個(gè)或者更多的任務(wù)的程序 ,多任務(wù)是通過(guò)分別指派一個(gè)單獨(dú)的線程來(lái)執(zhí)行一個(gè)任務(wù)來(lái)實(shí)現(xiàn)的。一個(gè)線程基本上就是一個(gè)程序代碼執(zhí)行的一個(gè)路徑,同時(shí)它也是在Win32調(diào)度器當(dāng)中可以識(shí)別的最小的執(zhí)行單元。一個(gè)進(jìn)程由一個(gè)或者多個(gè)線程以及程序的代碼、數(shù)據(jù)和程序中其他在內(nèi)存 中的資源組成。Windows的調(diào)度器決定哪個(gè)線程應(yīng)該運(yùn)行以及什么時(shí)候運(yùn)行。在多處理器計(jì)算機(jī)上,調(diào)度器可以通過(guò)切換單個(gè)線程到不同的處理器上來(lái)平衡CPU的負(fù)載。   在使用得當(dāng)?shù)那闆r下,多線程技術(shù)可以明顯提高一個(gè)應(yīng)用程序的性能和反應(yīng)速度,但如果使用不當(dāng),它也可以令一個(gè)程序變慢。經(jīng)驗(yàn)是設(shè)計(jì)多線程程序的最好的老師,沒(méi)有硬性規(guī)定什么時(shí)候應(yīng)該使用多線程技術(shù)。可以通過(guò)仔細(xì)檢查程序 ,來(lái)看看到底將哪個(gè)任務(wù)單獨(dú)放到另外的線程中運(yùn)行會(huì)更有用,又或者檢查看看哪些任務(wù)如果同時(shí)運(yùn)行會(huì)使程序更好,以上兩點(diǎn)都是比較好的切入點(diǎn)。   Visual Basic 是一個(gè)可以用來(lái)創(chuàng)建Windows應(yīng)用程序和 COM ActiveX組件的開(kāi)發(fā)工具。Visual Basic可以創(chuàng)建一個(gè)在COM STA 中安全運(yùn)行的ActiveX DLL組件。Visual Basic在創(chuàng)建組件提供兩種線程模型。它們是“ 單線程模型(Single Thread) ”(所有對(duì)象都運(yùn)行在一個(gè)STA中)以及“ 套間線程 模型(Apartment Threaded) ”(每個(gè)對(duì)象都可以在一個(gè)各自不同的STA中運(yùn)行)。盡管Visual Basic允許你創(chuàng)建基于套間線程模型的可以運(yùn)行在另外套間的ActiveX DLL對(duì)象,但它總是使用同一個(gè)線程在主STA中創(chuàng)建這些對(duì)象。因此使用ActiveX DLL對(duì)象的Visual Basic應(yīng)用程序是單線程的。   Thread Factory™ 提供一個(gè) 類(lèi)庫(kù),用于專(zhuān)門(mén) 通過(guò)在另外的STA套間中中創(chuàng)建COM對(duì)象(使用另外的線程) 的方式來(lái)克服這個(gè)限制。在不同于應(yīng)用程序主線程的線程中創(chuàng)建的對(duì)象,我們稱(chēng)之為 工作者對(duì)象(Worker Objects) (即上邊運(yùn)行著工作者對(duì)象的 工作者線程(Worker Thread) )。 Thread Factory 中有一個(gè) AsyncObject 對(duì)象( Asynchronous Object, 異步對(duì)象)是專(zhuān)門(mén)用來(lái)創(chuàng)建工作者對(duì)象的。一個(gè)AsyncObject對(duì)象是 一個(gè)輕型包裝類(lèi),它用于在一個(gè)單獨(dú)的STA套間中創(chuàng)建工作者對(duì)象,而且這個(gè)STA套間運(yùn)行在工作者線程上。除了創(chuàng)建工作者對(duì)象以外,AsyncObject對(duì)象也會(huì)接管所有和這個(gè)工作者對(duì)象有關(guān)的來(lái) 往傳輸?shù)腃OM調(diào)用、異步調(diào)用和異步調(diào)錯(cuò)誤處理。每個(gè)工作者對(duì)象都被一個(gè)單獨(dú)的AsyncObject對(duì)象包裹住。 備 注: Thread Factory只能夠?yàn)槟切┍粯?biāo)記為支持 Apartment線程模型 或者 兩者線程模型都同時(shí)支持 的對(duì)象創(chuàng)建工作者對(duì)象。   普通的Visual Basic對(duì)象創(chuàng)建過(guò)程(單線程)   Dim oPerson as clsPerson Set oPerson = new clsPerson '對(duì) 象在 主STA 及線程中創(chuàng)建   Thread Factory的對(duì)象創(chuàng)建過(guò)程(多線程)   Dim oAsyncObject as new ThreadFactoryLib.AsyncObject Dim oPerson as clsPerson Set oPerson = oAsyncObject.Create("PersonLib.clsPerson") '對(duì)象在另外的STA及線程中創(chuàng)建   當(dāng)用一個(gè) AsyncObject 對(duì)象創(chuàng)建一個(gè)新的 對(duì)象(工作者對(duì)象)時(shí), 開(kāi)始它首先會(huì)在一個(gè)另外的線程上創(chuàng)建一個(gè)新的STA套間,然后會(huì)在新的STA套間中創(chuàng)建工作者對(duì)象的一個(gè)實(shí)例。   備 注: 如果在一個(gè)工作者對(duì)象的代碼中使用 New 關(guān)鍵字來(lái)創(chuàng)建一個(gè)其他的新的工作者對(duì)象,那這個(gè)工作者對(duì)象會(huì)和新創(chuàng)建的工作者對(duì)象共享同一個(gè)套間和線程。   如果關(guān)閉應(yīng)用程序的主線程(主STA),則會(huì)引起所有的工作者都同時(shí)關(guān)閉。   要想產(chǎn)生一個(gè)真正的多線程程序,通過(guò)在一個(gè)另外的線程中運(yùn)行工作者對(duì)象這樣的方式這是不夠的。因?yàn)樗械腣isual Basic COM調(diào)用都是同步的,這使得兩個(gè)線程不可能同時(shí)運(yùn)行(除非你使用定時(shí)器來(lái)欺騙并且繞過(guò)COM)。AsyncObject對(duì)象公開(kāi)了一個(gè)稱(chēng)之為 IBlindDelegator 的特殊接口 ,這個(gè)接口使得要實(shí)現(xiàn) 異步調(diào)用變 更得更容易。這個(gè)IBlindDelegator接口會(huì)通過(guò)Begin_和Finish_方法來(lái)平行化COM+異步調(diào)用。   我們應(yīng)用異步調(diào)用時(shí),需要使用特殊的 錯(cuò)誤處理 方式。錯(cuò)誤一般是通過(guò)調(diào)用堆棧來(lái)返回的,而由異步調(diào)用引起的錯(cuò)誤會(huì)被丟棄,這是因?yàn)槟菚r(shí)的調(diào)用堆棧是空的,而那時(shí) 我們沒(méi)有一個(gè)可以將這個(gè)錯(cuò)誤返回的上層函數(shù)。由于所有由Thread Factory創(chuàng)建的工作者對(duì)象都在AsyncObject對(duì)象中包裝著,AsyncObject對(duì)象會(huì)攔截所有在異步調(diào)用時(shí)所產(chǎn)生的錯(cuò)誤并且將他們轉(zhuǎn)換為AsyncObject對(duì)象的OnError事件,這樣他們就不會(huì)被丟棄了。   在應(yīng)用程序的開(kāi)發(fā)過(guò)程中,最重要的步驟之一就是 調(diào)試 。Thread Factory通過(guò)允許你安全的使用Visual Basic的調(diào)試器,從而使調(diào)試變得簡(jiǎn)單。由于Visual Basic的調(diào)試器是為單線程應(yīng)用程序的調(diào)試而設(shè)計(jì)的,在調(diào)試不同的多線程應(yīng)用程序的項(xiàng)目時(shí),都會(huì)有一些不同的限制。   當(dāng)調(diào)試一個(gè)不包含ActiveX DLL源代碼的工程時(shí): 工作者對(duì)象會(huì)在一個(gè)另外的線程它自身的STA中創(chuàng)建,但這時(shí)這個(gè)被調(diào)試的工程當(dāng)中產(chǎn)生的所有對(duì)于這個(gè)工作者對(duì)象的調(diào)用都會(huì)被串行化(即使使用IBlindDelegator接口也是這樣)。 當(dāng)調(diào)試一個(gè)包括ActiveX DLL源代碼的工程時(shí): Visual Basic會(huì)強(qiáng)制所有的工作者對(duì)象都在同一個(gè)主STA線程中創(chuàng)建。調(diào)試這一類(lèi)項(xiàng)目是和調(diào)試一個(gè)普通的單線程應(yīng)用程序是一模一樣的。 提 示: 微軟的Visual C++調(diào)試器是一個(gè)非常好的用來(lái)代替Visual Basic的調(diào)試器的代替品。不像VB的調(diào)試器,Visual C++的調(diào)試器設(shè)計(jì)時(shí)就有考慮到多線程程序的調(diào)試。要使用Visual C++來(lái)調(diào)試VB的應(yīng)用程序和組件,你首先必須要編譯項(xiàng)目時(shí)勾選“產(chǎn)生符號(hào)化調(diào)試信息”選項(xiàng)。   備 注: 當(dāng)你運(yùn)行完一個(gè)多線程的工程后關(guān)閉Visual Basic的IDE時(shí),可能會(huì)引發(fā)一個(gè)異常(見(jiàn)下圖)。這是因?yàn)榭赡茉谡{(diào)試時(shí)我們?cè)?jīng)以按下“ 停止 ”按鈕的方式來(lái)中斷調(diào)試過(guò)程,又或者在程序代碼中包含了 END 語(yǔ)句 ,并且試圖以它來(lái)中斷程序的運(yùn)行。這會(huì)導(dǎo)致Visual Basic沒(méi)有正常讓清理代碼執(zhí)行,從而出現(xiàn)了下面的異常。這個(gè)異常只會(huì)在你關(guān)閉IDE時(shí)出現(xiàn), 絕對(duì)不會(huì)在運(yùn)行編譯后的程序時(shí)出現(xiàn) 。一個(gè)盡量減少這種偶然錯(cuò)誤的 方法是,在另外的DLL的工作者對(duì)象它們的創(chuàng)建和銷(xiāo)毀兩個(gè)事件過(guò)程中加入相應(yīng)的代碼來(lái)處理這些工作。這樣之所以能解決問(wèn)題,是因?yàn)轭?lèi)的Terminate事件它不管我們?cè)谑裁礃拥那闆r下退出程序 ,它都會(huì)被執(zhí)行。Progress示例程序就體現(xiàn)了這點(diǎn)(譯者注:這一段我沒(méi)有按照完全原文逐字翻譯,而按照我的理解來(lái)譯)。     當(dāng)要和一個(gè)另外的線程或者進(jìn)程通信時(shí)是會(huì)用到 封裝傳送 的。封裝傳送是在進(jìn)行超出線程或進(jìn)程邊界的方法調(diào)用時(shí),完成打包和發(fā)送的行為 的體現(xiàn)。COM使用 Proxy 和 Stub 來(lái)封裝傳送那些在線程/進(jìn)程間的調(diào)用。   在由Thread Factory所創(chuàng)建的工作者對(duì)象之間通信速度是非?斓模@是因?yàn)檫@些工作者對(duì)象都是在同一個(gè)進(jìn)程里的不同線程間運(yùn)行的(In-Process,進(jìn)程內(nèi))。 而 ActiveX EXE 服務(wù)器模型 雖然也可以創(chuàng)建工作者對(duì)象,但是 在它創(chuàng)建的進(jìn)程之間通信非常慢,這是因?yàn)樵谡{(diào)用外部進(jìn)程時(shí)會(huì)產(chǎn)生額外的針對(duì)調(diào)用的封裝傳送工作(Out-Of-Process,進(jìn)程外)。   下邊的插圖展示了在Thread Factory和ActiveX EXE服務(wù)器模型兩種模型中,產(chǎn)生與其他工作者對(duì)象通信的情況時(shí)涉及的封裝傳送流程。 Thread Factory Active X EXE 服務(wù)器模型   盡管Thread Factory和ActiveX EXE服務(wù)器模型兩種模型都是創(chuàng)建工作者對(duì)象的有效方法,但是Thread Factory提供很多ActiveX EXE服務(wù)器模型所沒(méi)有的優(yōu)勢(shì),包含: 性能:使用Thread Factory的通信速度快很多 內(nèi)置異步調(diào)用架構(gòu)(包含取消調(diào)用的操作) 內(nèi)置異步錯(cuò)誤處理 異步調(diào)用超時(shí)及強(qiáng)制結(jié)束處理 支持單獨(dú)檢查每個(gè)工作者對(duì)象的狀態(tài) 很容易在一個(gè)單獨(dú)的Visual Basic會(huì)話中調(diào)試 支持處理特定的資源,比如UDTs(譯者注:用戶自定義結(jié)構(gòu)體)、hDC句柄、臨界區(qū)域和hEvents句柄 支持創(chuàng)建多線程O(píng)CX組件 同步 問(wèn)題 在開(kāi)多線程應(yīng)用程序時(shí)非常重要 ,這是因?yàn)樗梢员WC線程的安全。我們通常使用同步對(duì)象來(lái)防止多個(gè)線程在同時(shí)訪問(wèn)同一個(gè)變量或者系統(tǒng)資源時(shí)發(fā)生沖突問(wèn)題。Thread Factory所創(chuàng)建的工作者對(duì)象,由COM所提供的一個(gè)特性來(lái)解決同步問(wèn)題,這個(gè)特性主要指COM強(qiáng)制所有的工作者對(duì)象都必須在自身的STA中創(chuàng)建,并且COM串行化所有 外部對(duì)它們的訪問(wèn)(譯者注:這一句是按照我自身的理解調(diào)整翻譯的,沒(méi)有完全按照原文一字一詞的譯)。串行化調(diào)用天生就很安全,這是因?yàn)樵诮o定的任意時(shí)刻只有內(nèi) ,所有的對(duì)于對(duì)象的調(diào)用當(dāng)中,只能有單個(gè)方法被執(zhí)行。如果兩個(gè)工作者對(duì)象試圖同時(shí)調(diào)用同一個(gè)另外的工作者對(duì)象的話,則兩者的調(diào)用都會(huì)被串行化,由此,只有其中一個(gè)工作者對(duì)象會(huì)得到他們想要的 ,而另一個(gè)工作者對(duì)象的操作則必須等到第一個(gè)調(diào)用操作執(zhí)行完成后才會(huì)被執(zhí)行。 很多時(shí)候在非線程安全的情況下,你必須手動(dòng)添加代碼以防止發(fā)生同時(shí)對(duì)一個(gè)資源或者對(duì)象的訪問(wèn)沖突問(wèn)題。在一個(gè)多線程程序中資源或者對(duì)象不是線程安全的原因有很多,但通常通過(guò)串行化訪問(wèn)可以使得它 們變成線程安全的。這可以通過(guò)使用Windows提供的眾多同步對(duì)象當(dāng)中的某一種來(lái)實(shí)現(xiàn)。為了方便起見(jiàn),Thread Factory在它的類(lèi)庫(kù)中提供了一些關(guān)于臨界區(qū)域(Critical Sections)和互斥量(Mutexes) 的API調(diào)用聲明(譯者注:你可以直接在VB中使用這些技術(shù)而且不需要額外的聲明它們)——詳見(jiàn) Win32 API調(diào)用 。   在開(kāi)發(fā)多線程應(yīng)用程序時(shí)全局變量是另外一個(gè)問(wèn)題,這是因?yàn)槿肿兞渴潜┞兜,這使得所有的對(duì)象(包括工作者對(duì)象)都可以同時(shí)訪問(wèn)它。Thread Factory的工作者對(duì)象使用 TLS (Thread Local Storage,線程本地存儲(chǔ)) 技術(shù),來(lái)每為個(gè)線程都保存一份全局變量的單獨(dú)的拷貝。   為了理解線程間的通信,我們有必要知道一個(gè)對(duì)象生存的地方(具體在哪個(gè)STA套間)。和一個(gè)工作者對(duì)象進(jìn)行任何的通信都總是被封裝傳送到工作者對(duì)象所在的對(duì)應(yīng)套間。每個(gè)STA套間都有它自己的線程,而且這個(gè)線程是 唯一一個(gè)可以讓存在于這個(gè)套間的任意對(duì)象執(zhí)行代碼的線程。   總之,Thread Factory提供了一個(gè)較全面的解決方案來(lái)用于創(chuàng)建健壯的多線程Visual Basic應(yīng)用程序。我們建議你把 指南 部份詳細(xì)閱讀一遍,它會(huì)引導(dǎo)你一步一步地完成創(chuàng)建工作者對(duì)象和對(duì)他們的異步調(diào)用的整個(gè)過(guò)程。 什么是Thread Factory™ ? Thread Factory™是一個(gè)用于讓程序員能非常容易的創(chuàng)建一個(gè)健壯的Visual Basic多線程應(yīng)用程序的一個(gè)DLL組件庫(kù)。Thead Factory使得Visual Basic程序員們能創(chuàng)建出運(yùn)行在另外的線程的ActiveX對(duì)象。Thead Factory也包含一個(gè)較完癢的用來(lái)調(diào)用和取消異步調(diào)用的框架。在Thead Factory眾多著名的特點(diǎn)當(dāng)中,最顯著的是它的健壯性、性能表現(xiàn)和易用性。Thead Factory遵循COM的所有規(guī)則,而且避免使用任何繞過(guò)COM組件的技術(shù)。和試圖通過(guò)創(chuàng)建另外的進(jìn)程來(lái)實(shí)現(xiàn)模擬多線程的ActiveX EXE服務(wù)器的產(chǎn)品不同,Thread Factory™會(huì)創(chuàng)建真正運(yùn)行在同一個(gè)進(jìn)程內(nèi)的多線程VB6應(yīng)用程序和組件。以下是兩種架構(gòu)的一個(gè)比較。 任務(wù)和特點(diǎn) Thread Factory™ ActiveX EXE 初始化與啟動(dòng) 快 (毫秒級(jí)) 慢 (通常5-15秒) 對(duì)象創(chuàng)建+Marshalling 快 (進(jìn)程內(nèi),In-Process) 非常慢 (進(jìn)程外,Out-Of-Process) 支持在單個(gè)VB6 IDE會(huì)話中調(diào)試 是 否 內(nèi)置異步調(diào)用 是 否 內(nèi)置異步錯(cuò)誤處理 是 否 內(nèi)置取消異步調(diào)用操作 是 否 支持創(chuàng)建多線程O(píng)CX 控件 是 否 要求實(shí)現(xiàn)特定的接口 否 是 共享進(jìn)程特定資源( hDC、hWnd、內(nèi)存地地址) 是 否   總之, Thread Factory™ 允許開(kāi)發(fā)者快速而經(jīng)濟(jì)的在所有微軟 Windows 操作系統(tǒng)上創(chuàng)建結(jié)實(shí)而快速的多線程應(yīng)用程序和組件。   新特色: 新的 AsyncControl OCX控件 支持通過(guò)微軟件的 Excel 和 Access 來(lái)創(chuàng)建真正的多線程 VBA 應(yīng)用程序 兼容.NET 新的 OnCancel 事件 新的 AutoClose Logic 新的 AsyncCall 方法,用于簡(jiǎn)化異步調(diào)用 新的 輔助函數(shù)類(lèi),詳見(jiàn) TFHelperFunctions 新的 GetThreadPriority 和 SetThreadPriority 方法 加強(qiáng)的在線文檔,包含修訂過(guò)的 程序員筆記 部份 加強(qiáng)的Win32 API 定義,詳見(jiàn) Win32 API 調(diào)用 一般特點(diǎn): 可安全使用VB6的IDE進(jìn)程調(diào)試 支持可使COM+平衡化的異步調(diào)用(Begin_ 和 End_) 加強(qiáng)的異步調(diào)用錯(cuò)誤處理 包含 StopWatchPro ™(譯者注:秒表)多線程O(píng)CX控件(現(xiàn)在支持VBA) 新的 ElapsedTime 類(lèi)——高精度定時(shí)(精確到1毫秒) 支持取消異步調(diào)用操作,詳見(jiàn) CancelObject 支持創(chuàng)建多線程O(píng)CX控件,詳見(jiàn) 多線程O(píng)CX控件示例 支持完全可配置的線程優(yōu)先級(jí)機(jī)制,詳見(jiàn) SetThreadPriority 新的 ThreadHANDLE 和 ThreadID 屬性 新的運(yùn)行時(shí)模塊(大小共160K),詳見(jiàn) 發(fā)布應(yīng)用程序 支持輕松訪問(wèn)有用的Win32 API函數(shù)和同步對(duì)象   架構(gòu): Thead Factory組件庫(kù)使用C++(ATL)和匯編語(yǔ)言開(kāi)發(fā)。 備 注: Thead Factory不依賴(lài)COM+或者任何MFC組件。 Thead Factory兼容COM技術(shù),并且能夠被如微軟的VB6、Excel、Access和.NET等不同的支持COM組件技術(shù)的開(kāi)發(fā)工具支持。 程序員如果是使用像Excel、Access或者.NET這樣的IDE環(huán)境來(lái)開(kāi)發(fā)程序的話,應(yīng)當(dāng)使用代理對(duì)象設(shè)計(jì)模式來(lái)進(jìn)行設(shè)計(jì)。
個(gè)人分類(lèi): access入門(mén)|8823 次閱讀|1 個(gè)評(píng)論
分享 Access菜鳥(niǎo)七大邪門(mén)武器之五:在access中使用多線程(二)
ganlinlao 2016-4-2 17:28
第二部分,理解com的套間線程 COM套間 為了理解com是如何處理線程的,讀者需要掌握套間的概念!套間在應(yīng)用中是一個(gè)邏輯容器,以使com對(duì)象可以分享遵循相同的線程訪問(wèn)規(guī)則(例在所屬線程中,規(guī)則限定了對(duì)象的方法和屬性在有套間和無(wú)套間情形中是如何被調(diào)用的)。套間本質(zhì)上只是一個(gè)概念,不像對(duì)象有自己的屬性和方法。沒(méi)有句柄類(lèi)型可以引用它,更沒(méi)有可調(diào)用的API操縱它。對(duì)于新手來(lái)說(shuō),這或許是套間難理解的一個(gè)重要原因,它是如此的抽象。 如果存在CoCreateApartment()和一些諸如CoEnterApartment()的API函數(shù),如果微軟提供帶有IApartment接口和操縱線程、對(duì)象方法的COM類(lèi),套間也許會(huì)很容易理解。從編程來(lái)說(shuō),似乎沒(méi)有切實(shí)的方式去洞察套間。為了幫助新手克服剛開(kāi)始學(xué)習(xí)套間遇到的困難,提供以下幾點(diǎn)參考: 1、套間由應(yīng)用創(chuàng)建,沒(méi)有直接創(chuàng)建或者檢查它們存在的函數(shù)。 2、線程和對(duì)象也通過(guò)應(yīng)用進(jìn)入套間并且參與套間相關(guān)的活動(dòng),沒(méi)有現(xiàn)成的函數(shù)完成這一過(guò)程。 3、套間模型十分像協(xié)議,或者需遵循的規(guī)則集合。 在多線程訪問(wèn)眾多com對(duì)象的操作系統(tǒng)中,我們?nèi)绾文鼙WC一個(gè)線程對(duì)com對(duì)象屬性、方法的調(diào)用結(jié)果不受另一線程對(duì)該對(duì)象訪問(wèn)的影響?為了解決上述問(wèn)題,com套間應(yīng)運(yùn)而生,它的出現(xiàn)就是為了保證所謂的現(xiàn)線程安全。通過(guò)套間,我們就可以保證本線程訪問(wèn)對(duì)象的內(nèi)部狀態(tài)免受其它線程訪問(wèn)的影響。 com中包含三種套間模型:?jiǎn)尉程套間、多線程套間和中性套間(Neutral Apartment),每一類(lèi)型套間代表了一種跨線程同步對(duì)象內(nèi)部狀態(tài)的機(jī)制。對(duì)于線程和對(duì)象,套間遵循以下的基本原則: 1、一個(gè)com對(duì)象僅且只能存在于一個(gè)套間。運(yùn)行時(shí)對(duì)象一經(jīng)創(chuàng)建就確定所屬套間,并且直到銷(xiāo)毀它一直存在于這個(gè)套間。 2、一個(gè)com線程(內(nèi)部創(chuàng)建了 com對(duì)象或者調(diào)用了com方法的線程)也屬于一個(gè)套間。與com對(duì)象一樣,線程從創(chuàng)建到結(jié)束都存在于同一套間。 3、屬于相同套間的線程和對(duì)象遵循相同的線程訪問(wèn)規(guī)則。套間內(nèi)部方法的調(diào)用是直接完成的,不需要com額外的輔助。 4、不同套間的線程和對(duì)象遵循了不同的線程訪問(wèn)規(guī)則。跨套間方法調(diào)用通過(guò)列集實(shí)現(xiàn),這就需要采用proxies和stubs。 除了保證線程安全,套間的另一好處是透明性,對(duì)象和客戶端都不需要關(guān)心他們采用的套間模型。底層的套間實(shí)現(xiàn)細(xì)節(jié)(特別是列集機(jī)制)由com子系統(tǒng)管理,開(kāi)發(fā)者無(wú)需關(guān)心。 (二)COM對(duì)象套間模型設(shè)定 從本段開(kāi)始一直到“EXE COM服務(wù)器和套間”,期間涉及的com對(duì)象都是在DLL服務(wù)器中實(shí)現(xiàn)。正如上面提到的,com對(duì)象僅屬于一個(gè)運(yùn)行的套間,在對(duì)象創(chuàng)建時(shí)就已經(jīng)確定。然而首先需要明白的是一個(gè)com對(duì)象是如何關(guān)聯(lián)它的套間模型的?對(duì)于DLL服務(wù)器中的com類(lèi),當(dāng)com線程實(shí)例化它時(shí),該類(lèi)會(huì)參考注冊(cè)表“InProcServer32”字段項(xiàng)“ThreadingModel”的字符串值。這個(gè)設(shè)置有開(kāi)發(fā)者控制。比如,當(dāng)你使用ATL開(kāi)發(fā)com對(duì)象時(shí),你可以指定對(duì)象在運(yùn)行時(shí)采用的線程模型。下面列舉了線程模型字符串值和代表的套間模型: 序號(hào) 注冊(cè)表值 套間模型 1 “Apartment” 單線程套間 2 “Single”或者空 Legacy單線程套間 3 “Free” 多線程套間 4 “Neutral” Neutral套間 5 “Both” 創(chuàng)建線程的套間模型 “Both”字符串值說(shuō)明com對(duì)象適用于單線程套間和多線程套間。 (三)COM線程套間模型設(shè)定 每一個(gè)com線程必須通過(guò)CoInitializeEx()函數(shù)并且設(shè)定參數(shù)為COINIT_APARTMENTTHREADED或者COINIT_MULTITHREADED進(jìn)行自身初始化。訪問(wèn)了CoInitializeEx()函數(shù)的線程即為com線程,也即線程已經(jīng)進(jìn)入套間。直到線程調(diào)用CoUninitialize()函數(shù)或者自身終止,才會(huì)離開(kāi)套間。 中性線程套間(NTA) Windows 2000引入了NTA用于性能優(yōu)化。進(jìn)行跨套間方法調(diào)用時(shí),進(jìn)入STA和MTA的方法調(diào)用引起的線程切換會(huì)占用大量開(kāi)銷(xiāo)。而進(jìn)入NTA的調(diào)用不會(huì)引起線程切換。如果STA或者M(jìn)TA線程調(diào)用同一個(gè)進(jìn)程中基于NTA的對(duì)象,線程會(huì)暫時(shí)離開(kāi)其套間,直接執(zhí)行NTA中的代碼。 注:因?yàn)樵谀承﹫?chǎng)合下,我們很有可能會(huì)遭遇到NTA的com組件,所以適當(dāng)留意一下。 單線程套間(STA) 注:因?yàn)閂BA只能創(chuàng)建和使用單線程套間,所以理解單線程套間是重點(diǎn)。 單線程套間只能包含一個(gè)線程(因此才稱(chēng)為單線程套間),但是可以包含多個(gè)對(duì)象。包含在單線程套間中的線程有一特別之處——如果線程中的對(duì)象需要導(dǎo)出給其它線程,那么它必須有自己的消息循環(huán)。線程通過(guò)調(diào)用CoInitializeEx()并指定函數(shù)參數(shù)為COINIT_APARTMENTTHREADED或者簡(jiǎn)介的調(diào)用CoInitizlize()函數(shù)(CoInitialize()函數(shù)實(shí)際上會(huì)調(diào)用參數(shù)設(shè)定為COINIT_APARTMENTTHREADED的CoInitializeEx())進(jìn)入單線程套間。進(jìn)入單線程套間的線程也可認(rèn)為已經(jīng)創(chuàng)建了那個(gè)套間(畢竟,在套間內(nèi)沒(méi)有其它線程第一次創(chuàng)建它)。通過(guò)指定“Apartment”到注冊(cè)表合適位置和在單線程套間內(nèi)實(shí)例化對(duì)象,我們可以說(shuō)com對(duì)象進(jìn)入單線程套間。 (一)STA線程訪問(wèn)規(guī)則 STA的線程訪問(wèn)規(guī)則如下: 1、所有的ST A 對(duì)象如STA線程一樣屬于相同的單線程套間。 2、STA內(nèi)的所有對(duì)象只接受該STA線程的方法調(diào)用。 第一點(diǎn)理解起來(lái)十分自然和簡(jiǎn)單。然而,請(qǐng)注意在相同DLL服務(wù)器、不同STA線程中創(chuàng)建的同一com類(lèi)的兩個(gè)對(duì)象屬于不同的套間。訪問(wèn)這兩個(gè)對(duì)象輸入跨套間,必須通過(guò)com的列集完成。至于第二點(diǎn),有兩種方式可以訪問(wèn)STA對(duì)象: 1、STA線程內(nèi)部訪問(wèn)。這種情形方法的調(diào)用被序列化。2、跨線程訪問(wèn)(也即跨套間)。這種情形下STA線程必須包含消息循環(huán),COM才能保證對(duì)象僅接受自身STA線程的方法訪問(wèn) 。 (二)STA中的消息循環(huán) 重要規(guī)則: STA線程需要消息循環(huán) 如果不理解單線程套間機(jī)制,這條規(guī)則看起來(lái)不那么明顯?蛻粽{(diào)用基于STA的對(duì)象時(shí),調(diào)用將被傳遞到STA中運(yùn)行的線程。COM通過(guò)向STA的隱藏窗口投遞消息來(lái)完成這種傳遞。那么,如果STA中的線程不接收和分發(fā)消息將發(fā)生什么?調(diào)用將在RPC通道中消失,永遠(yuǎn)也不返回。它將永遠(yuǎn)凋謝在STA的消息隊(duì)列中。 擁有消息循環(huán)的線程是UI線程,它關(guān)聯(lián)著一個(gè)或多個(gè)窗口,即線程擁有這些窗口。窗口對(duì)應(yīng)的窗口過(guò)程由所屬線程創(chuàng)建。任何線程發(fā)送或者發(fā)布消息給所有的窗口,但是只有目標(biāo)窗口過(guò)程才會(huì)響應(yīng)這些消息。發(fā)往目標(biāo)窗口的消息被同步,即窗口會(huì)保證按照消息發(fā)送或發(fā)布的順序接受處理消息。對(duì)于Windows程序開(kāi)發(fā)者來(lái)說(shuō),窗口處理過(guò)程不必是線程安全的。每一個(gè)窗口消息會(huì)轉(zhuǎn)化成原子操作,只有當(dāng)前消息處理完才能處理下一消息。在Windows系統(tǒng)中,這給com帶來(lái)與生俱來(lái)的優(yōu)點(diǎn):使得com對(duì)象輕松的實(shí)現(xiàn)線程安全。COM通過(guò)發(fā)布私有消息給對(duì)象關(guān)聯(lián)的隱藏窗口,實(shí)現(xiàn)外部套間對(duì)STA對(duì)象方法的調(diào)用。隱藏窗口的窗口過(guò)程安排處理對(duì)象的訪問(wèn)并且把結(jié)果返回調(diào)用者。 需要注意的是當(dāng)涉及到外部套間時(shí),COM總是引入porxies和stubs,如消息循環(huán)一樣兩者形成了單線程套間協(xié)議的一部分。有兩個(gè)重要的知識(shí)點(diǎn)需要關(guān)注: 1、系統(tǒng)只有當(dāng)訪問(wèn)來(lái)自外部套間時(shí),采用消息循環(huán)激發(fā)STA對(duì)象的方法才是可使用的。如果是來(lái)自套間內(nèi)的訪問(wèn),完全不需要com的參與,STA線程自身會(huì)保證調(diào)用以串行方式執(zhí)行。 2、如果STA線程從消息隊(duì)列中獲取或分發(fā)消息失敗,線程套間內(nèi)的com對(duì)象將無(wú)法接受套間之間的訪問(wèn)。 考慮到上述第2點(diǎn),諸如Sleep()、WaitForSingleObject()、WaitForMultipleObjects()等影響線程消息處理順序的函數(shù)是非常重要的。如果STA線程需要等待同步對(duì)象,為了保證消息循環(huán)不被打亂,必須采取特殊處理。 實(shí)際應(yīng)用中某些情形STA線程不需要包含消息循環(huán)。 (三)STA優(yōu)缺點(diǎn) 使用STA最大優(yōu)點(diǎn)就是簡(jiǎn)潔。對(duì)于com對(duì)象服務(wù)器來(lái)說(shuō),除了一些基本的代碼,參與的com對(duì)象和線程幾乎不需要同步代碼。com中所有方法的調(diào)用會(huì)自動(dòng)串行。因STA對(duì)象總是由相同線程訪問(wèn),即具有線程相似性。正是線程相似性,使得STA對(duì)象開(kāi)發(fā)者能夠采用線程局部存儲(chǔ)保存對(duì)象內(nèi)部數(shù)據(jù)的狀態(tài)。VB和MFC采用這種開(kāi)發(fā)技術(shù),所以它們開(kāi)發(fā)的對(duì)象是單線程套間對(duì)象。在需要支持legacy com組件的情形中采用STA是不可避免的。 任何事物都有兩面性,當(dāng)然使用STA也不例外點(diǎn)。 多個(gè)線程訪問(wèn)同一com對(duì)象時(shí)STA架構(gòu)嚴(yán)重降低了性能。 由于每一線程訪問(wèn)對(duì)象都會(huì)被串行化,所以線程必須等待其他占用線程的返回。等待時(shí)間降低了應(yīng)用響應(yīng)或者性能。如果線程包含過(guò)多的對(duì)象時(shí),STA架構(gòu)也會(huì)降低性能。 切記單線程套間包含一個(gè)線程和一個(gè)消息隊(duì)列,因而STA內(nèi)各個(gè)對(duì)象的訪問(wèn)由消息隊(duì)列串行化。 鑒于STA的優(yōu)缺點(diǎn),使用時(shí)必須根據(jù)實(shí)際應(yīng)用場(chǎng)景來(lái)選擇。 (四)STA COM對(duì)象及其服務(wù)器實(shí)現(xiàn) 對(duì)于開(kāi)發(fā)者,STA com對(duì)象的實(shí)現(xiàn)一般不必關(guān)心內(nèi)部成員數(shù)據(jù)的串行化訪問(wèn)。然而,單線程套間本身無(wú)法確保com DLL服務(wù)器的全局?jǐn)?shù)據(jù)和導(dǎo)出的全局函數(shù)(如DllGetClassObject()和DllCanUnloadNow())線程安全。切記com服務(wù)器對(duì)象可以在任何線程中創(chuàng)建,相同Dll服務(wù)器中的兩個(gè)對(duì)象可以在各自獨(dú)立的STA線程中創(chuàng)建,這保證了無(wú)需com的串行化機(jī)制服務(wù)器的全局?jǐn)?shù)據(jù)和函數(shù)可以由兩個(gè)不同線程正確訪問(wèn)。 另外,這種情形下線程的消息循環(huán)也無(wú)法提供任何幫助,畢竟這不是對(duì)象的內(nèi)部狀態(tài)(如果是程序就要付出代價(jià)啦),而是服務(wù)器內(nèi)部狀態(tài)。由于不同線程的對(duì)象可能訪問(wèn)服務(wù)器的全局變量和全局函數(shù),因而它們需要適當(dāng)串行化。這一規(guī)則也適用于類(lèi)的靜態(tài)成員函數(shù)和靜態(tài)成員變量。 一個(gè)眾所周知的com服務(wù)器變量是全局對(duì)象計(jì)數(shù),其由常用的全局函數(shù)DllGetClassObject()和DllCanUnloadNow()調(diào)用。API函數(shù)InterlockedIncrement()和InterlockedDecrement()可以同步不同線程對(duì)全局對(duì)象計(jì)數(shù)的訪問(wèn)。 總結(jié)STA服務(wù)器DLL實(shí)現(xiàn)的一般原則: 1、服務(wù)器dll必須有線程安全的標(biāo)準(zhǔn)入口函數(shù),比如函數(shù)DllGetClassObject()和DllCanUnloadNow()。 2、服務(wù)器dll的私有全局函數(shù)必須是線程安全的。 3、私有全局變量(特別是全局對(duì)象計(jì)數(shù))必須是線程安全的。 DllGetClassObject()函數(shù)功能是提供類(lèi)對(duì)象的訪問(wèn)。類(lèi)對(duì)象根據(jù)CLSID返回,并且通過(guò)它的接口(通常是IClassFactory)指針引用自身。DllGetClassObject()函數(shù)在API函數(shù)CoGetClassObject()內(nèi)部調(diào)用,com開(kāi)發(fā)者無(wú)需直接調(diào)用。通過(guò)類(lèi)對(duì)象的CLSID創(chuàng)建對(duì)象實(shí)例(由IClassFactory::CreateInstance()),我們可以把DllGetClassObject()函數(shù)視為com對(duì)象創(chuàng)建的開(kāi)關(guān),請(qǐng)記住該函數(shù)最重要的一點(diǎn):影響全局對(duì)象計(jì)數(shù)。我們調(diào)用函數(shù)DllGetClassObject(),可以獲得標(biāo)志com服務(wù)DLL包含對(duì)象是否仍舊存在和發(fā)揮功效。DllGetClassObject()函數(shù)根據(jù)全局對(duì)象計(jì)數(shù)決定返回值,如果com服務(wù)器dll中沒(méi)有對(duì)象存在,調(diào)用該函數(shù)可以從內(nèi)存中卸載com服務(wù)器DLL。 函數(shù)DLLGetClassObject()和DllCanUnLoadNow()必須是線程安全的以同步全局對(duì)象計(jì)數(shù)。一般來(lái)說(shuō),當(dāng)一個(gè)對(duì)象創(chuàng)建或者銷(xiāo)毀的時(shí)候,全局對(duì)象計(jì)數(shù)相應(yīng)增加或者減少。本文沒(méi)有對(duì)私有全局函數(shù)和全局變量的線程安全進(jìn)行詳細(xì)介紹,只能留給專(zhuān)家和經(jīng)驗(yàn)豐富者去探討。 對(duì)于com服務(wù)器來(lái)說(shuō),保證線程安全不是復(fù)雜的過(guò)程,許多情形下只需要簡(jiǎn)單的思考。上面的這些講解對(duì)于ATL COM服務(wù)器開(kāi)發(fā)者已經(jīng)足夠(除了私有全局?jǐn)?shù)據(jù)和全局函數(shù)的線程安全),所以他們只需要關(guān)注com對(duì)象的業(yè)務(wù)邏輯開(kāi)發(fā)。 (五)STA線程實(shí)現(xiàn) STA線程通過(guò)調(diào)用CoInitialize()或者CoInitializeEx(COINIT_APARTMENTTHREADED)進(jìn)行初始化自身。其次,如果線程創(chuàng)建的對(duì)象需要導(dǎo)出到其它線程(亦即其它套間),該線程需要提供消息循環(huán)處理com對(duì)象隱藏窗口的消息。切記com中隱藏窗口的窗口過(guò)程接受和處理這些私有消息,STA線程自身不會(huì)處理。如下的代碼示例了STA線程的框架: DWORD WINAPI ThreadProc(LPVOID lpvParamater) { /* Initialize COM and declare this thread to be an STA thread. */ ::CoInitialize(NULL); ... ... ... /* The message loop of the thread. */ MSG msg; while (GetMessage(msg, NULL, NULL, NULL)) { TranslateMessage(msg); DispatchMessage(msg); } ::CoUninitialize(); return 0; } STA線程十分像WinMain()函數(shù),實(shí)際上Windows應(yīng)用的WinMain()函數(shù)也運(yùn)行在線程中。你可以實(shí)現(xiàn)像WinMain()函數(shù)一樣的STA線程,即在消息循環(huán)之前創(chuàng)建窗口并保證窗口有合適的窗口過(guò)程。當(dāng)然,在窗口過(guò)程中你可以創(chuàng)建和管理com對(duì)象,也可以跨套間訪問(wèn)外部STA對(duì)象。如果你不想在線程中創(chuàng)建窗口,你仍然可以創(chuàng)建操縱對(duì)象并且跨套間訪問(wèn)外部線程的方法。 (六)不需要消息循環(huán)的STA線程 STA線程有時(shí)是不需要消息循環(huán)的,比如線程僅僅創(chuàng)建和使用對(duì)象而不供其它套間訪問(wèn)的情況。示例如下: int main() { ::CoInitialize(NULL); if (1) { ISimpleCOMObject1Ptr spISimpleCOMObject1; spISimpleCOMObject1.CreateInstance(__uuidof(SimpleCOMObject1)); spISimpleCOMObject1 - Initialize(); spISimpleCOMObject1 - Uninitialize(); } ::CoUninitialize(); return 0; } 上面的控制臺(tái)程序示例了主線程中創(chuàng)建STA而不需要消息循環(huán)。注意我們可以成功的調(diào)用Initialize()函數(shù)和Uninitialize()函數(shù),因它們?cè)赟TA內(nèi)部調(diào)用不要列集和消息循環(huán)的參與。但是,如果我們調(diào)用::CoInitializeEx(NULL, COINIT_MULTITHREADED)函數(shù),main()函數(shù)變成了多線程套間,同時(shí)程序發(fā)生了以下變化: 1、Initialize()函數(shù)和Uninitialize()函數(shù)的調(diào)用需要com列集的協(xié)助。 2、com對(duì)象spISimpleCOMObject1屬于com子系統(tǒng)創(chuàng)建的默認(rèn)STA套間而不是MTA套間。 3、main()線程本身仍然不要消息循環(huán),但是默認(rèn)STA需要。 4、調(diào)用Initialize()函數(shù)和Uninitialize()函數(shù)需要消息循環(huán)參與。 切記如果STA線程需要消息循環(huán),一定要保證該消息循環(huán)穩(wěn)定而不受中斷的執(zhí)行。 (七)示例聚焦STA 下面我們重點(diǎn)講解單線程套間。采用的方法是觀察com對(duì)象方法被調(diào)用時(shí)執(zhí)行線程的ID,對(duì)于標(biāo)準(zhǔn)STA對(duì)象,此ID就是STA對(duì)象的ID。如果一個(gè)STA對(duì)象不屬于創(chuàng)建它的線程(即這個(gè)線程不是STA線程),那么該線程ID與執(zhí)行對(duì)象方法的線程ID不匹配。 標(biāo)準(zhǔn)STA 以一個(gè)簡(jiǎn)單的例子講解標(biāo)準(zhǔn)STA。如下所示,例子包括一個(gè)簡(jiǎn)單的STA com對(duì)象,對(duì)應(yīng)的com類(lèi)CSimpleCOMObject2實(shí)現(xiàn)了TestMethod1()方法。TestMethod1()顯示正在執(zhí)行線程的ID消息框,代碼如下: STDMETHODIMP CSimpleCOMObject2::TestMethod1() { TCHAR szMessage ; sprintf (szMessage, "Thread ID : 0x%X", GetCurrentThreadId()); ::MessageBox(NULL, szMessage, "TestMethod1()", MB_OK); return S_OK; } 我們通過(guò)一個(gè)簡(jiǎn)單的測(cè)試程序?qū)嵗疌SimpleCOMObject2并調(diào)用方法TestMethod1,代碼如下: int main() { HANDLE hThread = NULL; DWORD dwThreadId = 0; ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); DisplayCurrentThreadId(); if (1) { ISimpleCOMObject2Ptr spISimpleCOMObject2; spISimpleCOMObject2.CreateInstance(__uuidof(SimpleCOMObject2)); spISimpleCOMObject2 - TestMethod1(); hThread = CreateThread ( (LPSECURITY_ATTRIBUTES)NULL, // SD (SIZE_T)0, // initial stack size (LPTHREAD_START_ROUTINE)ThreadFunc, // thread function (LPVOID)NULL, // thread argument (DWORD)0, // creation option (LPDWORD)dwThreadId // thread identifier ); WaitForSingleObject(hThread, INFINITE); spISimpleCOMObject2 - TestMethod1(); } ::CoUninitialize(); return 0; } 線程函數(shù)ThreadFunc()函數(shù)如下: DWORD WINAPI ThreadFunc(LPVOID lpvParameter) { ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); DisplayCurrentThreadId(); if (1) { ISimpleCOMObject2Ptr spISimpleCOMObject2A; ISimpleCOMObject2Ptr spISimpleCOMObject2B; spISimpleCOMObject2A.CreateInstance(__uuidof(SimpleCOMObject2)); spISimpleCOMObject2B.CreateInstance(__uuidof(SimpleCOMObject2)); spISimpleCOMObject2A - TestMethod1(); spISimpleCOMObject2B - TestMethod1(); } ::CoUninitialize(); return 0; } 線程函數(shù)中包含了展示當(dāng)前線程ID消息框的函數(shù)DisplayCurrentThreadId(),其代碼如下: /* Simple function that displays the current thread ID. */ void DisplayCurrentThreadId() { TCHAR szMessage ; sprintf (szMessage, "Thread ID : 0x%X", GetCurrentThreadId()); ::MessageBox(NULL, szMessage, "TestMethod1()", MB_OK); } 上面的例子創(chuàng)建了單線程套間,通過(guò)線程ID來(lái)解釋這一過(guò)程。從main()函數(shù)開(kāi)始,詳細(xì)分析如下: 1、main()函數(shù)通過(guò)調(diào)用CoInitializeEx和參數(shù)COINIT_APARTMENTTHREADED進(jìn)入單線程套間。由此在main()中創(chuàng)建的STA對(duì)象屬于main()線程的一部分。 2、main()函數(shù)調(diào)用DisplayCurrentThreadId()函數(shù),此時(shí)會(huì)顯示main()線程的ID,設(shè)為thread_id_1。 3、隨后程序?qū)嵗薱om類(lèi)SimpleCOMObject2,該對(duì)象和main()線程屬于相同的STA。 4、spISimpleCOMObject2的方法TestMethod1()顯示的線程ID肯定也是thread_id_1。隨后啟動(dòng)了線程和線程函數(shù)ThreadFunc(),主程序調(diào)用WaitForSingleObject()等待線程函數(shù)ThreadFunc()的結(jié)束。 5、線程函數(shù)ThreadFunc()再次調(diào)用CoInitializeEx和參數(shù)COINIT_APARTMENTTHREADED,同時(shí)進(jìn)入單線程套間。注意這里的STA套間是新創(chuàng)建的,不同于main()函數(shù)的STA。 6、線程函數(shù)中調(diào)用了DisplayCurrentThreadId()函數(shù),此時(shí)顯示的是ThreadFunc()線程的ID,設(shè)為thread_id_2。 7、隨后我們又創(chuàng)建了兩個(gè)com對(duì)象(spISimpleCOMObject2A和spISimpleCOMObject2B)。 8、線程函數(shù)中調(diào)用了兩個(gè)對(duì)象的方法TestMethod1()。 9、當(dāng)對(duì)象spISimpleCOMObject2A和spISimpleCOMObject2B調(diào)用各自的方法時(shí)運(yùn)行線程ID分別顯示。 10、它們顯示和函數(shù)ThreadFunc()相同的線程ID,即thread_id_2。 11、線程結(jié)束后放回主程序。 12、主程序中又一次調(diào)用方法TestMethod1(),當(dāng)然顯示的線程ID依然是thread_id_1。 通過(guò)示例需要記住的是:同一com類(lèi)的不同實(shí)例可以屬于不同獨(dú)立的STA。對(duì)于一個(gè)標(biāo)準(zhǔn)的STA模型,重要的是哪個(gè)套間實(shí)例化了該STA對(duì)象。這個(gè)例子不需要提供任何的消息徐循環(huán),因?yàn)楦鲗?duì)象都在他們自己的套間內(nèi),并沒(méi)有跨線程調(diào)用。即使我們?cè)趍ain()中調(diào)用了WaitForSingleObject()也不會(huì)遇到任何麻煩。 默認(rèn)STA 假如STA對(duì)象在一個(gè)非STA線程中創(chuàng)建情形會(huì)如何?下面的代碼示例了此種情況,其中com類(lèi) SimpleCOMObject2和函數(shù)DisplayCurrentThreadId()同第一個(gè)例子。 int main() { ::CoInitializeEx(NULL, COINIT_MULTITHREADED); DisplayCurrentThreadId(); if (1) { ISimpleCOMObject2Ptr spISimpleCOMObject2; /* If a default STA is to be created and used, it will be created */ /* right after spISimpleCOMObject2 (an STA object) is created. */ spISimpleCOMObject2.CreateInstance(__uuidof(SimpleCOMObject2)); spISimpleCOMObject2 - TestMethod1(); } ::CoUninitialize(); return 0; } 程序的詳細(xì)執(zhí)行過(guò)程如下: 1、main()函數(shù)調(diào)用CoInitializeEx(NULL, COINIT_MULTITHREADED),主線程初始化自身為多線程套間。 2、隨即程序調(diào)用DisplayCurrentThreadId()函數(shù),顯示了main()線程的ID。 3、接著STA對(duì)象spISimpleCOMObject2在線程中實(shí)例化。 4、注意對(duì)象spISimpleCOMObject2在非STA線程中初始化,它不屬于main()線程的MTA,屬于默認(rèn)的STA。 5、調(diào)用spISimpleCOMObject2對(duì)象的方法TestMethod1(),此時(shí)顯示的線程ID不是main()線程ID。 當(dāng)所有的STA對(duì)象在一個(gè)非STA線程中創(chuàng)建時(shí),它們自動(dòng)歸屬于一個(gè)默認(rèn)的STA,該STA是在創(chuàng)建對(duì)象時(shí)創(chuàng)建的。這時(shí)在創(chuàng)建對(duì)象的線程中獲得是對(duì)象的代理而非指針。注意,默認(rèn)的STA必須包含消息循環(huán),由com提供。 做為com套間世界的新人,必須注意:即使訪問(wèn)createInstance或者CoCreateInstance函數(shù),生成的對(duì)象有可能是在另一線程實(shí)例化的。com的這種行為對(duì)于用戶是完全透明的,請(qǐng)注意這些小的細(xì)節(jié),特別是在調(diào)試時(shí)。 Legacy STA Legacy STA屬于默認(rèn)的單線程套間,其對(duì)象屬于legacy com對(duì)象。Legacy意味著那些組件沒(méi)有任何線程的知識(shí),這些組件必須有設(shè)置成“Single”的ThreadingModel注冊(cè)表或者其它注冊(cè)表。Legacy STA對(duì)象比較重要的一點(diǎn)是這些對(duì)象的實(shí)例必須在相同的STA中創(chuàng)建,它們一直存在和運(yùn)行在這個(gè)legacy STA中,即使它們創(chuàng)建在以::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)初始化的線程中。 Legacy STA通常是進(jìn)程中的第一個(gè)單線程套間,如果一個(gè)legacy STA對(duì)象創(chuàng)建在任何STA模型之前,一個(gè)Legacy STA由com子系統(tǒng)自動(dòng)創(chuàng)建。開(kāi)發(fā)legacy STA對(duì)象好處在于這些對(duì)象實(shí)例的訪問(wèn)將被串行化,任何兩個(gè)legacy STA對(duì)象間的調(diào)用不要列集的參與。然而,非legacy STA的對(duì)象必須通過(guò)inter-apartment列集訪問(wèn)legacy STA對(duì)象,反之也是如此。我認(rèn)為這不是一個(gè)有吸引力的優(yōu)點(diǎn)。 另外,所有的Legacy STA對(duì)象必須僅能在相同的STA線程創(chuàng)建。 EXE COM服務(wù)器和套間 討論完Dll com服務(wù)器,為了文章的完整性接著介紹EXE COM服務(wù)器。首先介紹DLL服務(wù)器和EXE服務(wù)器的兩個(gè)重要差別。 差別1:對(duì)象創(chuàng)建的方式 當(dāng)COM創(chuàng)建定義在DLL中的com對(duì)象時(shí),必須加裝該dll,調(diào)用導(dǎo)出函數(shù)DllGetClassObject()獲得com對(duì)象工廠類(lèi)的IClassFactory接口指針。EXE服務(wù)器實(shí)際上也遵循這個(gè)過(guò)程:獲得com對(duì)象工廠類(lèi)的IClassFactory接口指針并通過(guò)它創(chuàng)建對(duì)象。那么DLL服務(wù)器和EXE服務(wù)器差別在那里? DLL服務(wù)器需要導(dǎo)出函數(shù)DllGetClassObject()以便com能夠提取類(lèi)工廠,而EXE服務(wù)器無(wú)需導(dǎo)出任何函數(shù),但要在啟動(dòng)時(shí)向com子系統(tǒng)注冊(cè)類(lèi)工廠,退出時(shí)銷(xiāo)毀。這一注冊(cè)過(guò)程通過(guò)調(diào)用API函數(shù)CoRegisterClassObject()實(shí)現(xiàn)。 差別2:對(duì)象套間模型聲明的方式 本文前面提到DLL服務(wù)器中的對(duì)象通過(guò)設(shè)置“InProcServer2”注冊(cè)表中的“ThreadingModel”字符串項(xiàng)聲明自己的套間。EXE服務(wù)器中的對(duì)象不用設(shè)置注冊(cè)表,注冊(cè)對(duì)象類(lèi)工廠的線程的套間模型決定了對(duì)象套間模型。 除了上述兩點(diǎn)差別,讀者還應(yīng)該注意的是DLL服務(wù)器中的STA對(duì)象有可能只服務(wù)于線程內(nèi)的調(diào)用,而來(lái)客戶端對(duì)EXE服務(wù)器對(duì)象的所有方法調(diào)用都以跨線程的方式實(shí)現(xiàn),這一過(guò)程需要使用列集和stubs,以及對(duì)象所屬套間線程的消息循環(huán)。
個(gè)人分類(lèi): access入門(mén)|2578 次閱讀|0 個(gè)評(píng)論
分享 Access菜鳥(niǎo)七大邪門(mén)武器之五:在access中使用多線程(一)。
ganlinlao 2016-4-2 17:02
在學(xué)習(xí)VBA的過(guò)程中,有三塊內(nèi)容基本不會(huì)觸及到,1、是編譯器(和宏定義);2、指針;3、多線程 接下來(lái)我們要談的是多線程的內(nèi)容。即在vba中如何使用多線程? 第一部分 理解多線程的基本概念: 當(dāng)前流行的Windows操作系統(tǒng)能同時(shí)運(yùn)行幾個(gè)程序(獨(dú)立運(yùn)行的程序又稱(chēng)之為進(jìn)程),對(duì)于同一個(gè)程序,它又可以分成若干個(gè)獨(dú)立的執(zhí)行流,我們稱(chēng)之為線程,線程提供了多任務(wù)處理的能力。用進(jìn)程和線程的觀點(diǎn)來(lái)研究軟件是當(dāng)今普遍采用的方法,進(jìn)程和線程的概念的出現(xiàn),對(duì)提高軟件的并行性有著重要的意義。現(xiàn)在的大型應(yīng)用軟件無(wú)一不是多線程多任務(wù)處理,單線程的軟件是不可想象的。因此掌握多線程多任務(wù)設(shè)計(jì)方法對(duì)每個(gè)程序員都是必需要掌握的。 一、了解線程   要講解線程,不得不說(shuō)一下進(jìn)程,進(jìn)程是應(yīng)用程序的執(zhí)行實(shí)例,每個(gè)進(jìn)程是由私有的虛擬地址空間、代碼、數(shù)據(jù)和其它系統(tǒng)資源組成。進(jìn)程在運(yùn)行時(shí)創(chuàng)建的資源隨著進(jìn)程的終止而死亡。線程的基本思想很簡(jiǎn)單,它是一個(gè)獨(dú)立的執(zhí)行流,是進(jìn)程內(nèi)部的一個(gè)獨(dú)立的執(zhí)行單元,相當(dāng)于一個(gè)子程序。單獨(dú)一個(gè)執(zhí)行程序運(yùn)行時(shí),缺省地包含的一個(gè)主線程,主線程以函數(shù)地址的形式出現(xiàn),提供程序的啟動(dòng)點(diǎn),如main()或WinMain()函數(shù)等。當(dāng)主線程終止時(shí),進(jìn)程也隨之終止。根據(jù)實(shí)際需要,應(yīng)用程序可以分解成許多獨(dú)立執(zhí)行的線程,每個(gè)線程并行的運(yùn)行在同一進(jìn)程中。   一個(gè)進(jìn)程中的所有線程都在該進(jìn)程的虛擬地址空間中,使用該進(jìn)程的全局變量和系統(tǒng)資源。操作系統(tǒng)給每個(gè)線程分配不同的CPU時(shí)間片,在某一個(gè)時(shí)刻,CPU只執(zhí)行一個(gè)時(shí)間片內(nèi)的線程,多個(gè)時(shí)間片中的相應(yīng)線程在CPU內(nèi)輪流執(zhí)行,由于每個(gè)時(shí)間片時(shí)間很短,所以對(duì)用戶來(lái)說(shuō),仿佛各個(gè)線程在計(jì)算機(jī)中是并行處理的。操作系統(tǒng)是根據(jù)線程的優(yōu)先級(jí)來(lái)安排CPU的時(shí)間,優(yōu)先級(jí)高的線程優(yōu)先運(yùn)行,優(yōu)先級(jí)低的線程則繼續(xù)等待。   線程被分為兩種:用戶界面線程和工作線程(又稱(chēng)為后臺(tái)線程)。用戶界面線程通常用來(lái)處理用戶的輸入并響應(yīng)各種事件和消息,其實(shí),應(yīng)用程序的主執(zhí)行線程對(duì)象就是一個(gè)用戶界面線程,當(dāng)應(yīng)用程序啟動(dòng)時(shí)自動(dòng)創(chuàng)建和啟動(dòng),同樣它的終止也意味著該程序的結(jié)束,進(jìn)程終止。工作線程用來(lái)執(zhí)行程序的后臺(tái)處理任務(wù),比如計(jì)算、調(diào)度、對(duì)串口的讀寫(xiě)操作等,對(duì)它來(lái)說(shuō)最重要的是如何實(shí)現(xiàn)工作線程任務(wù)的運(yùn)行控制函數(shù)。工作線程和用戶界面線程啟動(dòng)時(shí)要調(diào)用同一個(gè)函數(shù)的不同版本;最后需要讀者明白的是,一個(gè)進(jìn)程中的所有線程共享它們父進(jìn)程的變量,但同時(shí)每個(gè)線程可以擁有自己的變量。 歸納總結(jié): 什么是進(jìn)程? 當(dāng)一個(gè)程序開(kāi)始運(yùn)行時(shí),它就是一個(gè)進(jìn)程,進(jìn)程包括運(yùn)行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。 而一個(gè)進(jìn)程又是由多個(gè)線程所組成的。 什么是線程? 線程是程序中的一個(gè)執(zhí)行流,每個(gè)線程都有自己的專(zhuān)有寄存器(棧指針、程序計(jì)數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函數(shù)。 什么是多線程? 多線程是指程序中包含多個(gè)執(zhí)行流,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來(lái)執(zhí)行不同的任務(wù),也就是說(shuō)允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線程來(lái)完成各自的任務(wù)。 多線程的好處: 可以提高CPU的利用率。在多線程程序中,一個(gè)線程必須等待的時(shí)候,CPU可以運(yùn)行其它的線程而不是等待,這樣就大大提高了程序的效率。 多線程的不利方面: 線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多; 多線程需要協(xié)調(diào)和管理,所以需要CPU時(shí)間跟蹤線程; 線程之間對(duì)共享資源的訪問(wèn)會(huì)相互影響,必須解決競(jìng)用共享資源的問(wèn)題; 線程太多會(huì)導(dǎo)致控制太復(fù)雜,最終可能造成很多Bug; 二、線程的管理和操作 (一)線程的啟動(dòng) (二)線程的優(yōu)先級(jí) (三)線程的懸掛和恢復(fù) (四)結(jié)束線程 三、線程之間的通信 通常情況下,一個(gè)次級(jí)線程要為主線程完成某種特定類(lèi)型的任務(wù),這就隱含著表示在主線程和次級(jí)線程之間需要建立一個(gè)通信的通道。一般情況下,有下面的幾種方法實(shí)現(xiàn)這種通信任務(wù):使用全局變量、使用事件對(duì)象、使用消息。這里我們主要介紹后兩種方法。   (1) 利用用戶定義的消息通信 在Windows程序設(shè)計(jì)中,應(yīng)用程序的每一個(gè)線程都擁有自己的消息隊(duì)列,甚至工作線程也不例外,這樣一來(lái),就使得線程之間利用消息來(lái)傳遞信息就變的非常簡(jiǎn)單。 對(duì)于工作者線程,如果它的設(shè)計(jì)模式也是消息驅(qū)動(dòng)的,那么調(diào)用者可以向它發(fā)送初始化、退出、執(zhí)行某種特定的處理等消息,讓它在后臺(tái)完成。在控制函數(shù)中可以直接使用::GetMessage()這個(gè)SDK函數(shù)進(jìn)行消息分檢和處理,自己實(shí)現(xiàn)一個(gè)消息循環(huán)。GetMessage()函數(shù)在判斷該線程的消息隊(duì)列為空時(shí),線程將系統(tǒng)分配給它的時(shí)間片讓給其它線程,不無(wú)效的占用CPU的時(shí)間,如果消息隊(duì)列不為空,就獲取這個(gè)消息,判斷這個(gè)消息的內(nèi)容并進(jìn)行相應(yīng)的處理。 (2)用事件對(duì)象實(shí)現(xiàn)通信 在線程之間傳遞信號(hào)進(jìn)行通信比較復(fù)雜的方法是使用事件對(duì)象,用MFC的Cevent類(lèi)的對(duì)象來(lái)表示。事件對(duì)象處于兩種狀態(tài)之一:有信號(hào)和無(wú)信號(hào),線程可以監(jiān)視處于有信號(hào)狀態(tài)的事件,以便在適當(dāng)?shù)臅r(shí)候執(zhí)行對(duì)事件的操作。 四、線程之間的同步 前面我們講過(guò),各個(gè)線程可以訪問(wèn)進(jìn)程中的公共變量,所以使用多線程的過(guò)程中需要注意的問(wèn)題是如何防止兩個(gè)或兩個(gè)以上的線程同時(shí)訪問(wèn)同一個(gè)數(shù)據(jù),以免破壞數(shù)據(jù)的完整性。保證各個(gè)線程可以在一起適當(dāng)?shù)膮f(xié)調(diào)工作稱(chēng)為線程之間的同步。 (1) 臨界區(qū)   臨界區(qū)是保證在某一個(gè)時(shí)間只有一個(gè)線程可以訪問(wèn)數(shù)據(jù)的方法。使用它的過(guò)程中,需要給各個(gè)線程提供一個(gè)共享的臨界區(qū)對(duì)象,無(wú)論哪個(gè)線程占有臨界區(qū)對(duì)象,都可以訪問(wèn)受到保護(hù)的數(shù)據(jù),這時(shí)候其它的線程需要等待,直到該線程釋放臨界區(qū)對(duì)象為止,臨界區(qū)被釋放后,另外的線程可以強(qiáng)占這個(gè)臨界區(qū),以便訪問(wèn)共享的數(shù)據(jù)。臨界區(qū)對(duì)應(yīng)著一個(gè)CcriticalSection對(duì)象,當(dāng)線程需要訪問(wèn)保護(hù)數(shù)據(jù)時(shí),調(diào)用臨界區(qū)對(duì)象的Lock()成員函數(shù);當(dāng)對(duì)保護(hù)數(shù)據(jù)的操作完成之后,調(diào)用臨界區(qū)對(duì)象的Unlock()成員函數(shù)釋放對(duì)臨界區(qū)對(duì)象的擁有權(quán),以使另一個(gè)線程可以?shī)Z取臨界區(qū)對(duì)象并訪問(wèn)受保護(hù)的數(shù)據(jù)。同時(shí)啟動(dòng)兩個(gè)線程,它們對(duì)應(yīng)的函數(shù)分別為WriteThread()和ReadThread(),用以對(duì)公共數(shù)組組array[]操作 (2)互斥 互斥與臨界區(qū)很相似,但是使用時(shí)相對(duì)復(fù)雜一些,它不僅可以在同一應(yīng)用程序的線程間實(shí)現(xiàn)同步,還可以在不同的進(jìn)程間實(shí)現(xiàn)同步,從而實(shí)現(xiàn)資源的安全共享;コ馀cCmutex類(lèi)的對(duì)象相對(duì)應(yīng),使用互斥對(duì)象時(shí),必須創(chuàng)建一個(gè)CSingleLock或CMultiLock對(duì)象,用于實(shí)際的訪問(wèn)控制,因?yàn)檫@里的例子只處理單個(gè)互斥,所以我們可以使用CSingleLock對(duì)象,該對(duì)象的Lock()函數(shù)用于占有互斥,Unlock()用于釋放互斥。 (3)信號(hào)量 信號(hào)量的用法和互斥的用法很相似,不同的是它可以同一時(shí)刻允許多個(gè)線程訪問(wèn)同一個(gè)資源,創(chuàng)建一個(gè)信號(hào)量需要用Csemaphore類(lèi)聲明一個(gè)對(duì)象,一旦創(chuàng)建了一個(gè)信號(hào)量對(duì)象,就可以用它來(lái)對(duì)資源的訪問(wèn)技術(shù)。要實(shí)現(xiàn)計(jì)數(shù)處理,先創(chuàng)建一個(gè)CsingleLock或CmltiLock對(duì)象,然后用該對(duì)象的Lock()函數(shù)減少這個(gè)信號(hào)量的計(jì)數(shù)值,Unlock()反之。
個(gè)人分類(lèi): access入門(mén)|4474 次閱讀|0 個(gè)評(píng)論

QQ|站長(zhǎng)郵箱|小黑屋|手機(jī)版|Office中國(guó)/Access中國(guó) ( 粵ICP備10043721號(hào)-1 )  

GMT+8, 2025-7-13 07:28 , Processed in 0.068346 second(s), 15 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

返回頂部