本篇博文主要說說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,好像寫多了,代碼是簡化了,可讀性並沒有提升很多,也是給了一個方向。
