目 錄
第四章 設備驅動管理器的設計... 2
4.1 接口定義... 2
4.2 設備容器... 7
4.3 生成設備ID.. 7
4.4 對設備容器操作的互斥... 8
4.5 獲得設備列表... 8
4.6 設備計數器的特殊用處... 8
4.7 小結... 10
設備驅動管理器是對IRunDevice設備驅動接口的管理,是框架重要的組成部分之一。不管設備驅動管理器怎麼設計、以什麼形式存在,在概念上肯定是存在的。設計好的設備驅動管理器對於框架平台的穩定運行至關重要。
在介紹設備驅動管理器之前,先簡單介紹一下IO控制器(IIOController),它主要負責對IO和設備進行調度,並驅動設備運行,在《第5章 串口和網絡的IO設計》進行詳細的介紹。也就是說一個IO控制器可能會對應多個設備驅動(插件)。
早期的時候,每個IO控制器都有一個設備驅動管理器。在框架平台啟動的時候,根據設備驅動的通訊參數把相應的設備驅動分配到相應的IO管理器;當IO參數發生變化的時候,會觸發事件,把該設備驅動從當前IO控制器移動到另一個IO控制器。
從業務角度來考慮,這樣做並沒有什麼問題,並且一直運行的很穩定。但是,從模塊化、擴展性角度來考慮,不是太理想,如果在其他地方調用某一個設備驅動時,不能直接、很快的找到該設備驅動,必要遍歷IO控制器再匹配相應的設備驅動,並且操作麻煩以及效率不高。
在對框架平台進行重構的時候,把該問題進行了重新考慮,並把相關聯的問題一起解決。把每個IO控制器中的設備驅動管理器進行了整合,用一個設備驅動管理器來完成框架平台的協調工作。
這塊涉及到的技術並不難,也很容易理解,但是在設計過程中需要注意一些細節問題,這些問題可能影響框架平台的穩定性。
先定義一個接口(IDeviceManager<TKey, TValue>),確定設備驅動管理器都要完成什麼功能,增加設備、刪除設備、獲得設備和列表、以及其他的功能。接口代碼如下:
public interface IDeviceManager<TKey, TValue> : IEnumerable<TValue> where TValue : IRunDevice { /// <summary> /// 新建設備的ID,且唯一 /// </summary> /// <returns></returns> string BuildDeviceID(); /// <summary> /// 增加設備 /// </summary> /// <param name="key"></param> /// <param name="val"></param> void AddDevice(TKey key, TValue val); /// <summary> /// 刪除設備 /// </summary> /// <param name="key"></param> void RemoveDevice(TKey key); /// <summary> /// 移除所有設備 /// </summary> void RemoveAllDevice(); /// <summary> /// 獲得值集合 /// </summary> /// <returns></returns> List<TValue> GetValues(); /// <summary> /// 獲得關鍵字集合 /// </summary> /// <returns></returns> List<TKey> GetKeys(); /// <summary> /// 獲得設備的ID和名稱 /// </summary> /// <returns></returns> Dictionary<int, string> GetDeviceIDAndName(); /// <summary> /// 獲得高優先運行設備 /// </summary> /// <param name="vals"></param> /// <returns></returns> TValue GetPriorityDevice(TValue[] vals); /// <summary> /// 獲得單個設備 /// </summary> /// <param name="key"></param> /// <returns></returns> TValue GetDevice(TKey key); /// <summary> /// 獲得設備數組 /// </summary> /// <param name="para">IP或串口號</param> /// <param name="ioType">通訊類型</param> /// <returns></returns> TValue[] GetDevices(string para, CommunicationType ioType); /// <summary> /// 獲得指定IP和工作模式的網絡設備 /// </summary> /// <param name="remoteIP"></param> /// <param name="workMode"></param> /// <returns></returns> TValue[] GetDevices(string remoteIP, WorkMode workMode); /// <summary> /// 獲得指定工作模式的網絡設備 /// </summary> /// <param name="workMode"></param> /// <returns></returns> TValue[] GetDevices(WorkMode workMode); /// <summary> /// 獲得設備數組 /// </summary> /// <param name="ioType"></param> /// <returns></returns> TValue[] GetDevices(CommunicationType ioType); /// <summary> /// 按設備類型獲得設備 /// </summary> /// <param name="devType"></param> /// <returns></returns> TValue[] GetDevices(Device.DeviceType devType); /// <summary> /// 判斷設備是否存在 /// </summary> /// <param name="key"></param> /// <returns></returns> bool ContainDevice(TKey key); /// <summary> /// 根據輸入參數,判斷是否包括設備 /// </summary> /// <param name="para">IP或串口號</param> /// <param name="ioType">設備通訊類型</param> /// <returns></returns> bool ContainDevice(string para, CommunicationType ioType); /// <summary> /// 設置用戶級別 /// </summary> /// <param name="userlevel"></param> void SetUserLevel(UserLevel userlevel); /// <summary> /// 設置是否注冊 /// </summary> /// <param name="isreg"></param> void SetIsRegLicense(bool isreg); /// <summary> /// 獲得可用設備數 /// </summary> int Count { get; } /// <summary> /// 獲得設備的計數器的值 /// </summary> /// <param name="key"></param> ///<returns></returns> int GetCounter(TKey key); /// <summary> /// 設置計數器的值 /// </summary> /// <param name="key"></param> /// <param name="val"></param> void SetCounter(TKey key, int val); }
4.2 設備容器
設備驅動管理器是對Dictionary<Key,Value>的封裝,Key是設備驅動的ID,Value是IRunDevice設備驅動接口。設備驅動管理器需要跨線程應用,所以對Dictionary操作要加線程同步鎖。
當時使用的是.NET Framework 2.0框架,沒有ConcurrentDictionary(Of TKey, TValue)字典類,這個類的所有公共和受保護的成員都是線程安全的,使用原子性操作,適合多個線程之間同時使用。再重構時可以使用ConcurrentDictionary類代替Dictionary類,因為ConcurrentDictionary的所有操作使用到了Monitor線程同步類,不需要自己再進行封裝。
不貼ConcurrentDictionary類的源代碼了,具體使用參考MSDN。
查尋設備驅動管理器中最大的設備ID,並在此基礎上加1。這塊代碼很簡單,
如下:
public string BuildDeviceID() { if(_dic.Count>0) { int maxID=_dic.Max(d => d.Value.DeviceParameter.DeviceID); return (++maxID); } else { return 0; } }
增加設備驅動是需要生成設備ID,一般采用手動增加設備驅動,所以在這塊不需要加線程同步鎖。
框架平台所有組件要共享設備驅動管理器,所以會涉及到跨線程應用,特別
是當集合發生改變的時候,可能會出現異常。例如:啟動框架平台的時候,IO控制器已經啟動,IO控制器從設備驅動管理器提取自己的設備列表,但是這時有可能還沒有加載完設備驅動,當有新的設備驅動增加到設備驅動管理時,可能會引發沖突。
所以,在增加設備、刪除設備和獲得設備列表的時候增加了線程同步鎖,例如:lock (_SyncLock)。
有多個獲得設備的構造函數(GetDevices),主要是滿足不同的應用場景。
請參考“4.1接口定義”。
另外,獲得高優先運行設備的GetPriorityDevice函數在上一章節已經介紹了。
在接口定義中有SetCounter和GetCounter兩個函數,用在通訊過程中。
應用場景是這樣的,在並發和自控通訊模式中,設備驅動一直處於在通訊正常的情況下,但是突然發生線路中斷或其他原因導致無法接收到數據時,那麼設備驅動一直無法接收到數據,也無法對通訊狀態進行檢測以及改變相應的數據信息,也就是說現實情況已經發生改變,但是設備驅動卻無法得到響應。
為了防止這種情況的出現,設備驅動每次發送數據時,通過GetCounter函數獲得當前設備驅動的計數器,對計數器(變量)+1操作,並通過SetCounter函數把計數器(變量)再寫到設備驅動管理器中。在異常接收數據的時候,執行相同的流程,但是執行-1操作。如果一直發送數據,而沒有接收到數據時,當前設備驅動的計數器就會一直在累加。如果大於等於某個值的時候,就會通過RunIODevice(new byte[]{})驅動當前設備,執行整個設備處理流程,二次開發的代碼塊就會被調用,來完成此類應用場景的狀態改變和數據變化。代碼如下:
int counter = DeviceManager.GetInstance().GetCounter(dev.DeviceParameter.DeviceID.ToString()); int sendNum = SessionSocketManager.GetInstance().Send(dev.DeviceParameter.NET.RemoteIP, data); if (sendNum == data.Length && sendNum != 0) { DeviceMonitorLog.WriteLog(dev.DeviceParameter.DeviceName, "發送請求數據"); Interlocked.Increment(ref counter); } else { Interlocked.Increment(ref counter); DeviceMonitorLog.WriteLog(dev.DeviceParameter.DeviceName, "嘗試發送數據失敗"); } dev.ShowMonitorIOData(data, "發送"); if (counter >= 3) { try { dev.RunIODevice(new byte[] { }); } catch (Exception ex) { DeviceMonitorLog.WriteLog(dev.DeviceParameter.DeviceName, ex.Message); GeneralLog.WriteLog(ex); } Interlocked.Exchange(ref counter, 0); } DeviceManager.GetInstance().SetCounter(dev.DeviceParameter.DeviceID.ToString(), counter);
對於發送和接收數據會在不同的線程上完成,在對計數器(變量)進行+1和-1操作的時候使用到了Interlocked類,用於多個線程共享的變量提供原子操作,防止在多處理器上並行操作時可能引發的異常或數據遭到破壞。
這樣改造後,不僅可以在IO控制器對設備進行引用,也可以在其他組件使用。如果遇到類似的情況,希望使用ConcurrentDictionary類。
作者:唯笑志在
Email:[email protected]
QQ:504547114
.NET開發技術聯盟:54256083
文檔下載:http://pan.baidu.com/s/1pJ7lZWf
官方網址:http://www.bmpj.net