本篇博文主要說說hslcommunication的結果鏈的知識,說一下前因后果,以及目前最新的功能擴充,(V9.5.0以上)
以前也寫過一篇文章:https://www.cnblogs.com/dathlin/p/7865682.html 不看也沒事,參考這篇新的文章就好了。
首先還是聊聊,為什么會誕生這個 OperateResult ,比如我有個方法,獲取一些信息的,或是執行一些操作的,比如讀取文件的內容。
public string ReadFileContent( string path ) { return System.IO.File.ReadAllText( path ); }
很簡單吧,方法里面復雜也沒有關系的,如果這個方法保證不會發生異常,或是失敗,那就沒有關系,這樣寫也挺好的,但是事實就是極容易發生異常,就拿這個例子來說,可能因為文件不存在,可能因為其他異常。如果我們需要返回的內容包含下面三大塊,肯定包括 1. 是否成功 2.錯誤消息 3.內容 於是我加了一個錯誤碼,就有了下面的類(以下是簡寫)
public class OperateResult { public bool IsSuccess { get; set; } public string Message { get; set; } public int ErrorCode { get; set; } }
然后可能攜帶各種不同類型的結果內容,又可能是多個的,所以有了泛型的派生類,這算是泛型的一個經典的例子,另一個例子就是List<T>數組了。
public class OperateResult<T> : OperateResult public class OperateResult<T1, T2> : OperateResult public class OperateResult<T1, T2, T3> : OperateResult public class OperateResult<T1, T2, T3, T4> : OperateResult public class OperateResult<T1, T2, T3, T4, T5> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6, T7> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8, T9> : OperateResult public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : OperateResult
又定義了十個泛型類對象,最多可以攜帶10個不同類型的參數信息,當然,為了擴充一些轉化信息,整個 OperateResult.cs 文件的源代碼長達 3577 行源代碼。
好了,所以上面的方法可以改寫為:
public OperateResult<string> ReadFileContent( string path ) { try { return OperateResult.CreateSuccessResult( System.IO.File.ReadAllText( path ) ); } catch(Exception ex) { return new OperateResult<string>( ex.Message ); } }
這樣我們就能把結果信息返回了,當然了,實際可能更加復雜一點,比如下面所示,在讀取文件之前,還需要檢查當前賬戶是否有權限。
public bool CheckPermission( ) { // 檢查賬戶合法性,是否有權利下載 return true; } public OperateResult<string> ReadFileContent( string path ) { if (!CheckPermission( )) return new OperateResult<string>( "當前無權讀取文件的內容" ); try { return OperateResult.CreateSuccessResult( System.IO.File.ReadAllText( path ) ); } catch(Exception ex) { return new OperateResult<string>( ex.Message ); } }
到這里,已經成型基本的意思了。我們再來說一下HslCommunication自身的經典應用,我們來看一個三菱PLC的數據讀取示例,我們為了要讀取一個地址的原始字節數據,會提供這樣的方法,
public override OperateResult<byte[]> Read( string address, ushort length )
但是呢,實際上錯誤的原因是很多的,可能一開始地址輸入錯誤了,可能網絡發生了錯誤,可能PLC返回了一個錯誤碼,然后進行解析得到正確的數據。那么底層這么實現
public override OperateResult<byte[]> Read( string address, ushort length ) { // 獲取指令 var command = BuildReadCommand(address, length, false, PLCNumber); if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(command); // 核心交互 var read = ReadFromCoreServer(command.Content); if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(read); // 錯誤代碼驗證 OperateResult check = CheckResponseLegal( read.Content ); if (!check.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( check ); // 數據解析,需要傳入是否使用位的參數 return ExtractActualData(read.Content, false); }
我們再來看看這個核心交互是怎么實現的?
public OperateResult<byte[]> ReadFromCoreServer( byte[] send ) { var result = new OperateResult<byte[]>( ); OperateResult<Socket> resultSocket = null; InteractiveLock.Enter( ); try { // 獲取有用的網絡通道,如果沒有,就建立新的連接 resultSocket = GetAvailableSocket( ); if (!resultSocket.IsSuccess) { IsSocketError = true; AlienSession?.Offline( ); InteractiveLock.Leave( ); result.CopyErrorFromOther( resultSocket ); return result; } OperateResult<byte[]> read = ReadFromCoreServer( resultSocket.Content, send ); if (read.IsSuccess) { IsSocketError = false; result.IsSuccess = read.IsSuccess; result.Content = read.Content; result.Message = StringResources.Language.SuccessText; } else { IsSocketError = true; AlienSession?.Offline( ); result.CopyErrorFromOther( read ); } ExtraAfterReadFromCoreServer( read ); InteractiveLock.Leave( ); } catch { InteractiveLock.Leave( ); throw; } if (!isPersistentConn) resultSocket?.Content?.Close( ); return result; }
我們可以看到,一旦中間的某個環節發生了錯誤或是異常,這個錯誤信息會一直向上傳遞,直到傳遞給最上層的調用者。以此形成上下的鏈條。
那么Convert,Check,Then是什么意思呢?主要是簡化代碼的。我們來看看下面的代碼
public OperateResult<string> Write( ) { OperateResult write = siemens.Write( "M100", (short)12 ); if (!write.IsSuccess) return OperateResult.CreateFailedResult<string>( write ); return OperateResult.CreateSuccessResult( "M100寫入成功" ); }
這個代碼就可以簡化為:
public OperateResult<string> Write( ) => siemens.Write( "M100", (short)12 ).Convert<string>( "M100寫入成功" );
我們看到代碼簡化了很多,所以Convert意思就是,如果原來的結果對象失敗,就直接返回,如果成功,就返回給定的結果內容。
我們再來看第二種情況:這種情況主要是對讀取的內容進行一些判斷操作。
public OperateResult Check( ) { OperateResult<short> read = siemens.ReadInt16( "M100" ); if (!read.IsSuccess) return OperateResult.CreateFailedResult<string>( read ); if (read.Content == 10) return OperateResult.CreateSuccessResult( ); else return new OperateResult( "設備的數據值不對" ); }
這個代碼可以簡化為:
public OperateResult Check( ) => siemens.ReadInt16( "M100" ).Check( m => m == 10, "設備的數據值不對" );
當然,如果我的檢查的方法比較復雜,也可以這么寫:
public OperateResult CheckStatus(short value ) { if (value == 1) return new OperateResult( "錯誤原因1" ); if (value == 2) return new OperateResult( "錯誤原因2" ); if (value == 3) return new OperateResult( "錯誤原因3" ); if (value == 4) return new OperateResult( "錯誤原因4" ); return OperateResult.CreateSuccessResult( ); } public OperateResult Check( ) => siemens.ReadInt16( "M100" ).Check( m => CheckStatus( m ) );
我們再來看看一種更復雜的情況。
public OperateResult StartPLC( ) { // 這是一個啟動PLC的方法,邏輯就是,M100.0是啟動PLC,但是在啟動之前,需要向PLC的多個地址寫入初始參數。 OperateResult write = siemens.Write( "M200", (short)123 ); if (!write.IsSuccess) return write; write = siemens.Write( "M202", 123f ); if (!write.IsSuccess) return write; write = siemens.Write( "M206", "123456" ); if (!write.IsSuccess) return write; return siemens.Write( "M100.0", true ); }
嗯,這時候,就需要使用Then方法了,可以簡化為:
public OperateResult StartPLC( ) => siemens.Write( "M200", (short)123 ). Then( ( ) => siemens.Write( "M202", 123f ) ). Then( ( ) => siemens.Write( "M206", "123456" ) ). Then( ( ) => siemens.Write( "M100.0", true ) );
一旦發生失敗,就會立即回傳。現在我們來看個更復雜的綜合例子,這是一個現場流程中間的一個小環節,當AGV車到達庫位后,需要通知PLC進行連串的交互,以及讀取條碼信息:
string barcode = string.Empty; public OperateResult CheckSignalAfterAgvReach( ) { // 通知PLC信息,AGV已經到達 OperateResult write = siemens.Write( "DB101.3.1", true ); if (!write.IsSuccess) return write; // 等待PLC復位 允許AGV放胚信號 為false OperateResult wait = siemens.Wait( "DB101.3.2", false ); if (wait.IsSuccess) return wait; // 復位AGV放胚完成信號 write = siemens.Write( "DB101.3.1", false ); if (!write.IsSuccess) return write; // 等待允許讀取條碼信息 wait = siemens.Wait( "DB101.1.3", true ); if (wait.IsSuccess) return wait; // 讀取條碼的信息 var readBarCode = siemens.ReadString( "DB102.0" ); if (!readBarCode.IsSuccess) return readBarCode; // 條碼用於其他用途 barcode = readBarCode.Content; // 將上料讀取條碼完成值true write = siemens.Write( "DB101.1.4", true ); if (!write.IsSuccess) return write; // 等待上料允許讀取條碼設置為false wait = siemens.Wait( "DB101.1.3", false ); if (wait.IsSuccess) return wait; // 復位上料條碼讀取完成信號 return siemens.Write( "DB101.1.4", false ); }
那么這部分的代碼可以簡寫為:
public OperateResult CheckSignalAfterAgvReach2( ) => siemens.Write( "DB101.3.1", true ). Then( ( ) => siemens.Wait( "DB101.3.2", false ) ). Then( ( ) => siemens.Write( "DB101.3.1", false ) ). Then( ( ) => siemens.Wait( "DB101.1.3", true ) ). Then( ( ) => siemens.ReadString( "DB102.0" ) ). Then( m => { barcode = m; return siemens.Write( "DB101.1.4", true ); } ). Then( ( ) => siemens.Wait( "DB101.1.3", false ) ). Then( ( ) => siemens.Write( "DB101.1.4", false ) ); string barcode = string.Empty;
emmmm,好像寫多了,代碼是簡化了,可讀性並沒有提升很多,也是給了一個方向。