用實戰(zhàn):解決界面卡頓與并行處理難題)
1. 項目概述為什么異步調(diào)用是LabVIEW進階的必經(jīng)之路如果你在LabVIEW里寫過稍微復(fù)雜點的程序尤其是涉及到界面響應(yīng)、多任務(wù)并行或者硬件IO等待大概率會遇到一個頭疼的問題程序“卡”住了。前面板點不動進度條不更新整個VI虛擬儀器像死了一樣。這時候老手們通常會告訴你“你得用異步調(diào)用?!?異步調(diào)用聽起來像是個高深莫測的“黑魔法”但實際上它是LabVIEW從“單線程玩具”邁向“工業(yè)級應(yīng)用”的核心橋梁。簡單來說異步調(diào)用就是讓一個子VI我們稱之為“被調(diào)用方”在后臺獨立運行而調(diào)用它的主VI“調(diào)用方”不必傻等著它干完活可以立刻繼續(xù)執(zhí)行后面的代碼或者去響應(yīng)用戶的其他操作。這就像你讓助手去打印一份文件你不會站在打印機旁邊等而是可以回到工位繼續(xù)寫郵件。當打印完成或者過程中出錯助手會再來通知你。在LabVIEW的世界里這個“助手”就是一個獨立運行的VI實例而“通知”機制則是通過事件、隊列、通知器或者回調(diào)VI來實現(xiàn)的。為什么它如此重要看看那些熱搜詞就明白了“l(fā)abview上位機”要同時處理數(shù)據(jù)采集、界面刷新和網(wǎng)絡(luò)通信“l(fā)abview數(shù)據(jù)采集”可能一邊讀卡一邊存盤一邊顯示“l(fā)abview生產(chǎn)者消費者模式”更是異步思想的經(jīng)典架構(gòu)。不用異步這些場景要么界面卡頓用戶體驗極差要么無法充分利用多核CPU性能程序效率低下。更別提那些“l(fā)abview安裝錯誤”、“生成的安裝包fatal error”等問題很多時候正是因為程序架構(gòu)不合理在同步調(diào)用鏈中某個環(huán)節(jié)卡死導(dǎo)致的連鎖反應(yīng)。所以無論你是想解決“前面板點不動”的燃眉之急還是打算設(shè)計一個穩(wěn)健的“l(fā)abview狀態(tài)機”或“生產(chǎn)者消費者”架構(gòu)亦或是進行“l(fā)abview與匯川PLC通訊”、“l(fā)abview與西門子S7通訊”這類需要等待硬件響應(yīng)的操作深入理解異步調(diào)用都是你無法繞開的一課。接下來我將拋開教科書式的定義從一個實踐者的角度帶你拆解異步調(diào)用的幾種核心方法、它們背后的“為什么”、以及我踩過無數(shù)坑才總結(jié)出的“怎么用”。2. 異步調(diào)用的核心機制與方案選型在LabVIEW里實現(xiàn)異步本質(zhì)上是在管理多個并行的執(zhí)行線程和它們之間的通信。NINational Instruments提供了幾種內(nèi)置的機制每種都有其特定的適用場景和“脾氣”。選錯了方案可能會帶來內(nèi)存泄漏、難以調(diào)試的競態(tài)條件或者性能瓶頸。2.1 異步調(diào)用方案全景圖與選型邏輯首先我們得搞清楚有哪些“牌”可以打。最常見的三種方式是通過引用調(diào)用Call By Reference、VI服務(wù)器動態(tài)調(diào)用以及異步調(diào)用節(jié)點Asynchronous Call Node。很多人容易把它們混淆其實它們的出發(fā)點和能力邊界截然不同。通過引用調(diào)用CBR這是最基礎(chǔ)、最直接的異步方式。你獲得一個VI的引用Reference然后告訴LabVIEW“去運行這個VI?!?之后主VI就繼續(xù)往下走了。它的控制力較弱通常需要配合其他通信機制如隊列、通知器、全局變量來獲取子VI的運行結(jié)果或狀態(tài)。它適合那些“發(fā)射后不管”或結(jié)果通過獨立通道返回的任務(wù)。VI服務(wù)器動態(tài)調(diào)用這是基于LabVIEW強大的VI服務(wù)器架構(gòu)功能更豐富。你可以動態(tài)地打開一個VI引用設(shè)置其前面板控件值運行它并在運行中或運行后獲取其前面板數(shù)據(jù)。它比CBR更“重”但也更靈活可以實現(xiàn)諸如動態(tài)加載插件、運行時修改界面等高級功能。對于單純的異步執(zhí)行有時顯得有點“殺雞用牛刀”。異步調(diào)用節(jié)點ACN這是LabVIEW專門為異步操作設(shè)計的節(jié)點通常位于“編程”-“應(yīng)用程序控制”選板。它本質(zhì)上是CBR的一種封裝和增強提供了更清晰的接口來傳遞輸入數(shù)據(jù)、獲取輸出數(shù)據(jù)和處理錯誤。它通過內(nèi)置的“通知器”機制在子VI結(jié)束時回調(diào)是NI推薦的、更現(xiàn)代的異步調(diào)用方式。選型心法求簡單、求輕量如果子VI不需要向主VI返回復(fù)雜數(shù)據(jù)或者數(shù)據(jù)通過其他渠道如數(shù)據(jù)流傳遞首選通過引用調(diào)用。求規(guī)范、求可靠如果子VI需要返回明確的結(jié)果且希望有標準的錯誤處理流程異步調(diào)用節(jié)點是最佳選擇。它的代碼可讀性更好生命周期管理更清晰。求動態(tài)、求控制如果需要運行時決定調(diào)用哪個VI或者需要操作子VI的前面板那么必須使用VI服務(wù)器動態(tài)調(diào)用。對于大多數(shù)工業(yè)上位機、測試測量應(yīng)用我的經(jīng)驗是優(yōu)先考慮異步調(diào)用節(jié)點ACN。因為它平衡了易用性、健壯性和性能是構(gòu)建清晰異步架構(gòu)的基石。下面我們就以ACN為核心深入它的五臟六腑。2.2 異步調(diào)用節(jié)點ACN的解剖輸入、輸出與生命周期一個標準的異步調(diào)用節(jié)點在程序框圖上看起來像是一個特殊的子VI節(jié)點。你需要連接幾個關(guān)鍵端子VI引用指向你要異步運行的VI。這個VI必須事先設(shè)置好“可重入”屬性后面會詳細講。輸入?yún)?shù)如果被調(diào)用的VI有輸入控件這里可以連線傳入初始值。錯誤輸入標準錯誤簇用于鏈式錯誤處理。輸出會返回一個“調(diào)用者引用Caller Refnum”。這個引用是后續(xù)所有操作的唯一憑證務(wù)必妥善保管例如存入移位寄存器或全局變量。錯誤輸出標準錯誤簇。這里有一個至關(guān)重要的概念生命周期。當你啟動一個異步調(diào)用LabVIEW會在內(nèi)存中創(chuàng)建一個該VI的獨立實例因為它是可重入的。這個實例會一直存在直到發(fā)生以下兩件事之一1它自己運行完畢2你通過“停止異步調(diào)用”節(jié)點顯式終止它。如果你啟動了異步調(diào)用但忘了管理它的引用和生命周期就會導(dǎo)致“僵尸VI”實例常駐內(nèi)存這就是內(nèi)存泄漏的典型原因長期運行的程序會因此越來越慢直至崩潰。實操心得我習(xí)慣為每一個異步任務(wù)創(chuàng)建一個專用的“任務(wù)控制簇”里面包含“調(diào)用者引用”、“任務(wù)狀態(tài)枚舉”、“錯誤信息”和“結(jié)果數(shù)據(jù)”。這個簇被放入一個功能全局變量FGV或通過引用訪問的隊列中統(tǒng)一管理。這樣無論在程序的哪個角落我都能查詢或控制任何一個異步任務(wù)。3. 異步調(diào)用的核心細節(jié)與實戰(zhàn)配置理解了核心機制我們進入實戰(zhàn)環(huán)節(jié)。如何配置一個VI用于異步調(diào)用如何啟動、監(jiān)控和結(jié)束它這里每一步都有坑。3.1 被調(diào)用VI的“可重入”屬性配置這是異步調(diào)用的前提。右鍵點擊要被異步調(diào)用的VI圖標選擇“屬性”進入“執(zhí)行”類別。重入執(zhí)行必須選擇“共享副本重入”或“預(yù)分配副本重入”。共享副本LabVIEW會維護一個實例池需要時分配用完后回收。適合短時間、高頻調(diào)用的任務(wù)內(nèi)存利用率高但實例狀態(tài)不保持。預(yù)分配副本在調(diào)用開始時創(chuàng)建獨立實例結(jié)束時銷毀。每個實例都有獨立的數(shù)據(jù)空間。適合長時間運行或需要保持內(nèi)部狀態(tài)的任務(wù)如一個獨立的控制循環(huán)。對于大多數(shù)異步任務(wù)我推薦使用“預(yù)分配副本”因為它邏輯更清晰避免了實例池管理帶來的潛在交叉干擾。打開時運行切勿勾選異步調(diào)用的VI必須由調(diào)用方啟動如果勾選此項VI引用一打開就會自動運行失去控制。調(diào)用時掛起通常不勾選。如果勾選則異步調(diào)用啟動后VI處于暫停狀態(tài)需要額外代碼來恢復(fù)運行用于特殊調(diào)試場景。3.2 啟動異步調(diào)用與數(shù)據(jù)傳遞配置好VI后在調(diào)用方使用“異步調(diào)用節(jié)點”。數(shù)據(jù)傳遞在這里是“一次性”的。你在節(jié)點輸入端連線提供的值是子VI啟動時的初始輸入。如果子VI運行過程中主VI的數(shù)據(jù)發(fā)生了變化不會自動傳遞給正在運行的子VI實例。這是異步通信需要解決的第一個問題如何傳遞動態(tài)數(shù)據(jù)解決方案是使用隊列Queue、通知器Notifier或用戶事件User Event。例如主VI可以將命令和數(shù)據(jù)放入一個隊列而異步運行的子VI內(nèi)部有一個循環(huán)不斷從該隊列中取出命令執(zhí)行。這就是“生產(chǎn)者-消費者”模式的異步變體。一個關(guān)鍵技巧你可以在啟動異步調(diào)用時將一個隊列的引用作為參數(shù)傳遞給子VI。這樣主VI和子VI就共享了這個通信通道。[主VI] - [創(chuàng)建隊列] - [異步調(diào)用節(jié)點傳入隊列引用] - [繼續(xù)執(zhí)行...] | v [子VI實例循環(huán)“出列”執(zhí)行命令]3.3 結(jié)果的獲取回調(diào)與輪詢子VI跑完了結(jié)果怎么拿異步調(diào)用節(jié)點本身不直接返回子VI的輸出。你需要使用“等待異步調(diào)用結(jié)束”節(jié)點?;卣{(diào)模式推薦這是ACN的優(yōu)雅之處。在“異步調(diào)用節(jié)點”的右鍵菜單中可以選擇“連接回調(diào)VI”。你可以指定一個專門的“回調(diào)VI”。當異步任務(wù)正常結(jié)束或因錯誤而停止時LabVIEW會自動調(diào)用這個回調(diào)VI并將子VI的輸出數(shù)據(jù)和錯誤信息傳遞給它。在回調(diào)VI里你可以處理結(jié)果、更新界面、觸發(fā)下一個任務(wù)等。這實現(xiàn)了真正的異步通知效率最高。輪詢模式如果你沒有設(shè)置回調(diào)也可以在主VI的某個循環(huán)中使用“等待異步調(diào)用結(jié)束”節(jié)點并設(shè)置一個超時時間例如0毫秒。如果超時前任務(wù)結(jié)束該節(jié)點返回True并輸出結(jié)果如果未結(jié)束返回False。你可以根據(jù)返回值決定是處理結(jié)果還是繼續(xù)做別的事。這種方式需要自己寫循環(huán)查詢不夠高效但有時在簡單場景下夠用。注意事項回調(diào)VI是在子VI的線程上下文中執(zhí)行的這意味著回調(diào)VI里不能直接操作主VI前面板的控件跨線程操作控件會導(dǎo)致競爭或崩潰。如果需要更新界面必須使用“控件引用”結(jié)合“調(diào)用節(jié)點”在UI線程執(zhí)行屬性/方法或者使用“用戶事件”通知主VI循環(huán)去更新?;卣{(diào)VI應(yīng)盡可能快地執(zhí)行完畢不要在里面做耗時操作否則會阻塞子VI線程的釋放。3.4 錯誤處理與任務(wù)終止異步調(diào)用的錯誤處理是兩層級的啟動錯誤連接“異步調(diào)用節(jié)點”的錯誤輸出端可以捕獲到“VI引用無效”、“內(nèi)存不足”等立即發(fā)生的錯誤。運行錯誤子VI內(nèi)部發(fā)生的錯誤會通過其自身的錯誤輸出簇傳遞。在回調(diào)模式中這個錯誤簇會傳給回調(diào)VI。在輪詢模式中會通過“等待異步調(diào)用結(jié)束”節(jié)點輸出。如何強制終止一個異步任務(wù)使用“停止異步調(diào)用”節(jié)點并傳入之前保存的“調(diào)用者引用”。這個操作會向子VI發(fā)送一個停止請求但子VI是否立即停止取決于其內(nèi)部實現(xiàn)。如果子VI是一個簡單的順序代碼它會執(zhí)行完當前幀后退出。如果子VI內(nèi)部有一個While循環(huán)你需要在循環(huán)條件中檢查“停止異步調(diào)用”節(jié)點產(chǎn)生的“停止”狀態(tài)通過“獲取異步調(diào)用狀態(tài)”節(jié)點或回調(diào)中的錯誤狀態(tài)。一個健壯的子VI應(yīng)該能響應(yīng)這個停止請求。// 偽代碼示意子VI內(nèi)部的健壯循環(huán) BOOL stopRequested FALSE; ERROR err NoError; WHILE (NOT stopRequested AND NOT err) { // 執(zhí)行工作... // 檢查外部停止信號可通過隊列、通知器或檢查異步調(diào)用狀態(tài)獲得 stopRequested CheckExternalStopSignal(); // 處理內(nèi)部錯誤 err DoWork(); } // 循環(huán)退出后將錯誤信息如果有和結(jié)果傳遞出去4. 異步調(diào)用在典型場景下的實戰(zhàn)應(yīng)用理論說再多不如看實戰(zhàn)。我們結(jié)合幾個熱搜詞里的典型場景看看異步調(diào)用如何落地。4.1 場景一響應(yīng)式上位機界面解決“前面板卡死”問題在“l(fā)abview上位機”中點擊一個按鈕開始執(zhí)行一個耗時計算如數(shù)據(jù)分析、報表生成界面直接“凍住”直到計算完成。同步做法按鈕事件回調(diào)中直接調(diào)用耗時VI。異步做法在按鈕事件回調(diào)中不直接調(diào)用耗時VI。創(chuàng)建一個“任務(wù)命令隊列”。將耗時VI的引用和所需參數(shù)打包成一個消息放入隊列。事件回調(diào)立即結(jié)束界面恢復(fù)響應(yīng)。后臺有一個獨立的“工作者循環(huán)”消費者從隊列中取出任務(wù)。工作者循環(huán)使用異步調(diào)用節(jié)點啟動耗時VI并指定一個回調(diào)VI。耗時VI在后臺運行。完成后回調(diào)VI被觸發(fā)將結(jié)果通過“用戶事件”或“控件引用調(diào)用”發(fā)送回主界面線程進行更新。這樣用戶點擊后界面立刻有反饋如按鈕變灰、進度條開始動畫計算在后臺進行計算完成后結(jié)果自動刷新到界面。這就是“l(fā)abview生產(chǎn)者消費者模式”與異步調(diào)用的完美結(jié)合。4.2 場景二并行硬件通信與數(shù)據(jù)采集問題“l(fā)abview與匯川PLC通訊”和“l(fā)abview數(shù)據(jù)采集”需要同時與多個設(shè)備通信或者一邊采集一邊保存。同步做法的局限如果用順序結(jié)構(gòu)讀PLC、讀采集卡、存盤、顯示……所有操作串行總時間等于各環(huán)節(jié)之和效率極低。異步做法為每個獨立硬件任務(wù)創(chuàng)建獨立的異步VI例如一個VI專門負責通過Snap7庫與西門子PLC通信對應(yīng)“snap7 labview 專用封裝庫”另一個VI專門負責通過DAQmx讀取數(shù)據(jù)采集卡。主VI作為協(xié)調(diào)者主VI啟動這些異步通信VI并傳遞給它們各自的命令隊列。數(shù)據(jù)匯流每個異步通信VI將采集到的數(shù)據(jù)通過各自的流通道如隊列、流盤寫入函數(shù)發(fā)送給一個專門的數(shù)據(jù)處理或存儲VI。這個數(shù)據(jù)處理VI本身也可以是異步的。錯誤聚合每個異步VI都有自己的錯誤輸出主VI需要監(jiān)聽通過回調(diào)或輪詢這些錯誤并進行統(tǒng)一處理。這樣做PLC通信、數(shù)據(jù)采集、數(shù)據(jù)存盤、界面刷新這些任務(wù)在物理時間上真正并行充分利用多核CPU系統(tǒng)吞吐量大幅提升。4.3 場景三長時間運行的后臺服務(wù)問題需要開發(fā)一個后臺日志服務(wù)對應(yīng)“l(fā)abview日志記錄編程”持續(xù)監(jiān)控系統(tǒng)狀態(tài)并記錄到文件且不能影響主程序的性能。異步做法創(chuàng)建一個“日志記錄器.vi”設(shè)置為“預(yù)分配副本重入”。其內(nèi)部是一個While循環(huán)從日志隊列中取出消息并寫入文件。在主程序初始化時使用異步調(diào)用節(jié)點啟動這個“日志記錄器.vi”并將一個全局日志隊列的引用傳遞給它。保存好調(diào)用者引用。程序任何地方需要寫日志只需向這個全局日志隊列放入一條消息?!叭罩居涗浧?vi”在后臺異步運行持續(xù)消費隊列中的消息。主程序退出時通過保存的調(diào)用者引用向日志隊列發(fā)送一個“退出”命令并調(diào)用“停止異步調(diào)用”等待日志器優(yōu)雅關(guān)閉。這種模式將耗時的文件IO操作與主程序邏輯完全解耦主程序幾乎感覺不到日志記錄的開銷。5. 異步調(diào)用常見問題與深度排查實錄即使理解了原理實戰(zhàn)中依然會踩坑。下面是我在項目支援和社區(qū)答疑中總結(jié)的最高頻問題。5.1 內(nèi)存泄漏與“僵尸VI”現(xiàn)象程序長時間運行后內(nèi)存占用持續(xù)增長最終可能報錯“內(nèi)存不足”。根因異步調(diào)用啟動后未管理引用啟動了異步調(diào)用但既沒有等待它結(jié)束也沒有停止它引用丟失導(dǎo)致VI實例無法被釋放。循環(huán)內(nèi)不當創(chuàng)建在快速循環(huán)中不斷啟動新的異步調(diào)用而舊的任務(wù)還未結(jié)束。隊列、事件等資源未釋放傳遞給異步VI的隊列、通知器引用在異步VI結(jié)束后沒有正確關(guān)閉。排查與解決使用“應(yīng)用程序內(nèi)存”工具在LabVIEW菜單中選擇“工具”-“性能分析”-“顯示緩沖區(qū)分配”然后運行程序。觀察“VI實例”和“數(shù)據(jù)空間”數(shù)量的變化。如果它們只增不減基本可以確定有泄漏。強制回收在程序退出前或定期維護中使用“停止異步調(diào)用”節(jié)點可傳入無效引用它會嘗試停止所有來清理。但這是治標關(guān)鍵是找到泄漏點。最佳實踐為每個異步任務(wù)建立生命周期管理表如前文提到的任務(wù)控制簇。啟動、暫停、停止、銷毀都有明確路徑。使用“獲取所有異步調(diào)用”函數(shù)可以列出當前所有活動調(diào)用輔助調(diào)試。5.2 界面更新崩潰或延遲現(xiàn)象在異步任務(wù)的回調(diào)VI中直接更新前面板控件程序偶爾崩潰或者界面更新非常慢。根因違反了“UI操作必須在UI線程執(zhí)行”的原則?;卣{(diào)VI運行在子VI的線程中直接操作屬于主VI線程的控件是跨線程操作會引發(fā)競爭。解決方案使用用戶事件User Event在回調(diào)VI中不直接更新控件而是發(fā)出一個攜帶數(shù)據(jù)用戶事件。主VI的事件結(jié)構(gòu)中注冊了這個事件在事件回調(diào)中更新控件。這是最標準、最安全的方式。使用控件引用調(diào)用節(jié)點在回調(diào)VI中獲取控件的引用然后使用“調(diào)用節(jié)點”選擇“調(diào)用者線程中運行”的方法來設(shè)置屬性。這本質(zhì)上是將操作任務(wù)派發(fā)回了控件所屬的線程。使用隊列傳遞更新命令和用戶事件類似將更新命令和數(shù)據(jù)放入一個專用的“界面更新隊列”由主VI的循環(huán)來消費并執(zhí)行更新。5.3 “可重入”VI的靜態(tài)數(shù)據(jù)沖突現(xiàn)象當多個異步實例同時運行同一個可重入VI時如果VI內(nèi)部使用了未初始化的移位寄存器、功能全局變量FGV或未受保護的共享資源會導(dǎo)致數(shù)據(jù)混亂。根因誤解了“共享副本重入”和“預(yù)分配副本重入”的數(shù)據(jù)隔離范圍?!邦A(yù)分配副本”每個實例有自己的數(shù)據(jù)空間包括前面板控件默認值、未初始化的移位寄存器。但是如果VI內(nèi)部調(diào)用了另一個非重入的子VI或者訪問了全局變量、FGV那么這些資源是跨實例共享的需要加鎖保護?!肮蚕砀北尽睂嵗g可能復(fù)用數(shù)據(jù)空間絕對不能在移位寄存器中保存狀態(tài)信息。避坑指南對于需要保持內(nèi)部狀態(tài)的長時間運行異步VI務(wù)必使用“預(yù)分配副本”。在異步VI內(nèi)部如果需要進行跨實例的共享數(shù)據(jù)訪問例如向一個全局配置字典讀取數(shù)據(jù)必須使用信號量Semaphore或隊列進行同步防止競態(tài)條件。避免在異步VI內(nèi)部使用非重入的子VI除非你能確保該子VI是線程安全的通常意味著它無狀態(tài)只進行純計算。5.4 錯誤鏈斷裂現(xiàn)象異步任務(wù)中發(fā)生了錯誤但主程序完全沒有感知程序在錯誤狀態(tài)下繼續(xù)運行產(chǎn)生錯誤結(jié)果。根因沒有建立有效的錯誤傳遞鏈路。異步調(diào)用節(jié)點的錯誤輸出只反映“啟動”錯誤。子VI運行中的錯誤必須通過回調(diào)VI或等待節(jié)點顯式獲取并處理。構(gòu)建健壯的錯誤處理鏈統(tǒng)一錯誤出口設(shè)計異步VI時確保所有錯誤路徑都匯聚到其錯誤輸出簇?;卣{(diào)VI處理在回調(diào)VI中第一個動作就是檢查傳入的錯誤簇。如果有錯誤根據(jù)錯誤代碼和來源決定是記錄日志、通知用戶還是嘗試恢復(fù)。全局錯誤處理器考慮建立一個全局的錯誤處理異步服務(wù)。所有回調(diào)VI中的錯誤都發(fā)送給這個服務(wù)由它統(tǒng)一決定如何記錄文件、網(wǎng)絡(luò)、如何報警界面彈窗、郵件。超時機制對于任何異步調(diào)用都應(yīng)該設(shè)置一個合理的超時時間通過“等待異步調(diào)用結(jié)束”節(jié)點的超時輸入。防止因為死鎖、硬件無響應(yīng)等原因?qū)е氯蝿?wù)永遠掛起。6. 高級模式異步調(diào)用與狀態(tài)機、Actor框架的結(jié)合當你熟練掌握了基礎(chǔ)的異步調(diào)用后可以嘗試將其與更高級的軟件設(shè)計模式結(jié)合構(gòu)建出極其清晰、強大的應(yīng)用。6.1 異步狀態(tài)機傳統(tǒng)的狀態(tài)機如“l(fā)abview狀態(tài)機”是在一個While循環(huán)內(nèi)順序執(zhí)行各個狀態(tài)。如果某個狀態(tài)如“等待設(shè)備響應(yīng)”耗時很長整個狀態(tài)機就會阻塞。異步狀態(tài)機的改進在于將耗時的狀態(tài)操作封裝成一個異步調(diào)用。狀態(tài)機在進入該狀態(tài)時啟動異步任務(wù)然后立即跳轉(zhuǎn)到一個“等待結(jié)果”狀態(tài)。在“等待結(jié)果”狀態(tài)中狀態(tài)機不阻塞它可以輪詢或通過事件監(jiān)聽異步任務(wù)是否完成。一旦完成根據(jù)結(jié)果跳轉(zhuǎn)到下一個狀態(tài)。這樣做狀態(tài)機本身始終保持響應(yīng)可以處理其他事件如用戶取消命令而耗時的IO操作在后臺并行。這非常適合需要與多個外部設(shè)備交互的復(fù)雜流程控制。6.2 基于Actor模型的異步框架Actor模型是一種更徹底的并發(fā)模型。每個Actor都是一個獨立的計算實體它有自己的狀態(tài)只通過消息隊列與其他Actor通信并且一次只處理一條消息。在LabVIEW中我們可以用一個持續(xù)運行的異步VI來模擬一個Actor這個VI內(nèi)部是一個消息循環(huán)從自己的專屬消息隊列中取出消息處理。主程序或其他Actor通過向這個隊列發(fā)送消息來驅(qū)動它。這個Actor VI可以再異步啟動其他的子任務(wù)。例如在一個數(shù)據(jù)采集系統(tǒng)中采集Actor負責與采集卡通信收到“開始采集”消息后異步啟動一個高速讀卡的循環(huán)并將數(shù)據(jù)塊發(fā)送給“處理Actor”。處理Actor收到數(shù)據(jù)塊后進行濾波、分析等計算然后將結(jié)果發(fā)送給“存儲Actor”和“顯示Actor”。存儲Actor負責將數(shù)據(jù)寫入文件或數(shù)據(jù)庫。顯示Actor負責更新前面板圖表。所有Actor都是獨立、異步運行的通過消息隊列松耦合。這種架構(gòu)的擴展性極強添加新功能只需增加新的Actor修改現(xiàn)有功能只需修改對應(yīng)Actor的內(nèi)部邏輯彼此影響最小。從簡單的“不卡界面”需求到復(fù)雜的多設(shè)備并行測控系統(tǒng)異步調(diào)用都是LabVIEW程序員工具箱里最鋒利的工具之一。它要求你從“線性流程”思維轉(zhuǎn)向“事件驅(qū)動、并發(fā)協(xié)作”思維。開始時會覺得復(fù)雜但一旦掌握你設(shè)計的程序在健壯性、響應(yīng)速度和資源利用率上都會有質(zhì)的飛躍。記住管理好生命周期、處理好線程間通信、建立清晰的錯誤傳播路徑是寫好異步程序的不二法門。下次當你面對一個需要等待的硬件操作或一個耗時的計算任務(wù)時別再讓主循環(huán)空轉(zhuǎn)了試試把它扔到后臺去異步執(zhí)行吧。