黎穎智,史彩霞,劉世學
(廣西氣象服務中心,廣西 南寧 530022)
全國綜合氣象信息共享系統(CIMISS)是中國氣象局建設的集氣象資料采集、加工處理、存儲管理和共享服務等功能于一身的氣象信息業務平臺。能夠為國、省、地、縣各級氣象科研部門提供規范統一高效的氣象數據。雖然氣象數據統一訪問接口(MUSIC),為各級應用系統提供了包括實時觀測數據、各業務單位產品數據、和整編歷史數據在內的基礎數據的接入服務[1-4]。但是由于部分科研項目或專業服務所需要數據格式的不同或者所需要的數據需通過進行基礎數據再加工,CIMISS提供的數據有時候并不能直接拿來使用。另外,由于CIMISS資料部署在國家和省級數據中心,地縣級部門在使用數據時也可能會到受網絡異常因素的影響。因此,如何在不影響用戶體驗的前提下,采用異步多線程的方法快速從CIMISS中讀取數據并加工錄入到本地數據庫中便是本文需要解決的問題。
氣象數據統一服務接口(MUSIC)提供多種不同的服務方式,包括客戶端調用服務、web service、REST服務和腳本服務。提供多種語言的客戶端開發包,包括 C#、Java、C/C++、Fortran、PHP、Python 等。本文以REST服務和C#為例用于開發研究[1-4]。REST服務用于從CIMISS中提取數據到本機內存對象中,而C#5.0新特性async和await進行異步操作實現主線程與方法線程的并行執行,解決提取CIMISS數據時出現的主界面無響應問題,批量數據的快速入庫則采用SqlBulkCopy來實現。
如圖1所示,線程的生命周期分為新建、就緒、運行、阻塞、死亡五個階段。單線程程序,是順序執行的,前一操作是否順暢會影響到后面的操作,一旦發生阻塞便會使整個程序無法操作。如采用多線程異步執行則可避免阻塞。多線程是使用多個處理句柄同時對多個任務進行控制處理的一種特別的形式,但多線程使用了更小的資源開銷。異步是指調用某一操作后,后面的操作可不等待其結果繼續執行,如后面無其他操作則當前線程將會睡眠,其他線程在此時則可調用cpu資源。在異步操作完成后通過回調函數的方式獲取通知與結果[5-6]。在C#編程中實現多線程的方法有以下幾種:
(1)直接用Thread類創建;Thread newThread =new Thread(new ThreadStart(() =>{})),參數為一個ThreadStart類型的委托。這是最簡單的方法,但使用該方法創建的線程難于管理,若建立過多的線程反會影響系統性能,而且Thread對象也無法解決對于有返回值類型的委托的問題。
(2)線程池(thread pool):線程池是通過線程共享與回收機制來減少性能開銷。當程序要新建線程來執行任務時,線程池才初始化一個線程。在完成任務以后,該線程不會自行銷毀,而是以掛起的方式返回到線程池中等待程序再次向線程池發出請求時再度被激活。這樣既節省了建立線程形成的性能損耗,也可以讓多任務復用同一線程,從而節省大量的開銷。
(3)Task方式:此方式通常用于需要循環執行任務并需要獲取執行后的結果。Task最大的優點就是任務能夠控制task的執行順序,使多個任務有序運行。與Thread對象相比,Task對象則可輕松解決對于有返回值類型的委托的問題。
線程池與Task的比較:
線程池中每一次QueueUserWorkItem的使用都會產生一個工作項進入全局隊列進行排隊,最后線程池中的的工作線程以FIFO(First Input First Output)的形式取出,任務委托的線程池不光有全局隊列,而且每一個工作線程都有局部隊列。當線程不足時,線程池就會創建新的線程來執行任務,直到線程池達到最大線程數(線程池滿),當FIFO十分頻繁時,會造成很大的線程管理開銷。而Task在嵌套的場景下,當局部隊列中有多個task,某個task的線程執行完任務時,該空閑線程就會從同一隊列中以FIFO的形式分流和負載其他任務,從而減少了線程管理的開銷。這些優勢都是線程池所無法比擬的。

圖1 線程的生命周期
異步多線程編程最大的問題是狀態、結果跟蹤(即數據同步問題),在c#5.0之前,多線程編程相對于單線程會出現一個特有的問題,就是線程安全的問題。所謂的線程安全就是如果代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的。線程安全問題都是由全局變量及靜態變量引起的[7-8]。為了保證多線程情況下,訪問靜態變量的安全,可以用鎖機制來保證,但Lock只能鎖住一個引用類型的對象。c#5.0中加入了async和await方法來保證線程安全,async/await將多個線程進行串行處理,等到await之后的語句執行完成后才執行本線程的其他語句。async/await使得建立一個同時具備可讀性與可維護性的異步解決方案變得很簡單。以下是用async/await編寫的定時自動錄入數據線程的主要實現代碼:


如上代碼所示,在c#5.0中定義異步方法和定義同步方法一樣簡單,在timer1_Tick使用關鍵字await可讓<DataTable>和<ListViewItem>任務線程在后臺運行而不會堵塞UI線程,避免出現UI主界面卡死的現象出現。
SqlBulkCopy功能非常強大,具有快速且高效能夠批量插入數據性能,較之傳統的使用SQL語句至少快數十倍。SqlBulkCopy類僅用于向SQL Server表中寫入數據。但是數據的來源卻不局限SQL Server,只要能載入DataTable實例讀取的任何數據來源均可。Microsoft SQL Server數據庫的bcp的常用命令行應用工具提供了用于快速批量復制錄入大文件數據到庫表或視圖以及格式文件導出等功能。SqlBulkCopy類可以編寫提供與其相似的功能的托管代碼解決方案。
在SqlBulkCopy類出現前,C#在Sqlserver中批量插入數據一般采用的是使用sql語句insert循環逐條插入的方法,經過實驗發現插入一百萬條數據,大概需要52分鐘左右,每插入一條數據耗時約3毫秒。而采用SqlBulkCopy插入同樣數量級的數據僅耗時8秒。由此可見與常用insert語句相較,在需要插入數十萬百萬數據的時候,利用insert插入的速度十分慢的。其原因除了SqlBulkCopy原理是采用了SQL Server的bcp協議進行數據的批量復制之外,還在于insert語句在for循環中直接進行數據庫操作,數據庫的每一次連接、打開及關閉都是相當耗時的,雖然在C#中存在數據庫連接池,也就是當使用using或者conn.Close()進行釋放連接時,其實并非真正關閉數據庫連接,連接以類似于休眠的方式存在,當需要再次操作的時候,會從連接池將其喚醒。而SqlBulkCopy無需使用循環,便不存在這類的損耗。
隨著CIMISS在氣象領域的應用不斷加深,各級氣象部門中無論是普通業務還是科研項目研究都將不可避免的涉及到CIMISS的使用,創建一個保證數據安全并提供快速穩定的數據接入環境是十分必要的,本文通過探討如何采用多線程異步編程快速錄入數據,為各氣象業務與科研項目在解決類似問題時提供了一種可以參考的可行技術方案。
參考文獻:
[1]曹威,張冰松,李鑫.基于CIMISS的省市縣三級氣象信息傳輸監控系統的設計與實現[J].信息與電腦(理論版),2017,(21):73-76..
[2]李志鵬,胡佳軍,楊立苑,李顯風,鄧衛華.基于CIMISS的氣象數據處理時效監視系統設計與實現[J].氣象與減災研究,2016,39(04):309-313.
[3]王宏記,楊代才.基于CIMISS的長江流域氣象水文信息共享系統設計與實現研究 [J].安徽農業科學,2014,42(32):11565-11570.
[4]]榮裕良,張霞,馬忠芬,薛正平.松江智慧氣象為農服務系統開發研究[J].氣象研究與應用,2017,38(1):102-106.
[5]C#程序設計經典教程 [M].清華大學出版社,羅福強,2011.
[6] 陳翠娥,王學伶.C# 屬性、特性和反射的應用研究[J].電腦與電信,2015,(9):51-53.
[7]彭慶喜,陳軍威,周威.基于C#多線程的Web實體抽取設計與實現[J].軟件導刊,2013,12(01):84-86.
[8]秦江林,符合,楊秀好,楊忠武,羅同基,雷秀峰.林業病蟲害氣象服務系統的創新設計與應用 [J].氣象研究與應用,2017,38(2):57-60.
[9]石濤,劉軍,張麗,陳金華.基于GIS和意愿調查法的氣象為農服務效益評估 [J]. 氣象研究與應用,2016,37(4):86-89+131.