我想實現以下目標:
將數據寫入outputStream並等待,直到接收或發送下壹個數據,直到10秒。如果後續數據到達其他返回數據,則返回零。
我試著這樣做:
-(APDUResponse *)sendcommandanwaitforresponse:(ns data *)請求{
APDUResponse *結果;
如果(!設備總線& amp& amp請求!=零){
deviceIsBusy = YES
timedOut = NO
responseReceived =否;
if([[my ses output stream]hasSpaceAvailable]){
[NSThread detachNewThreadSelector:@ selector(start time out)to target:self with object:nil];
[[mySes output stream]write:[請求字節]maxLength:[請求長度]];
而(!時間輸出& amp& amp!接收到的響應){
睡眠(2);
NSLog(@“打勾”);
}
if(response received & amp;& amp回應!=零){
結果=響應;
響應=零;
}
[我的計時器無效];
myTimer = nil
}
}
deviceIsBusy = NO
返回結果;
}
- (void) startTimeout {
NSLog(@“啟動超時”);
my timer =[n timer timerWithTimeInterval:10.0 target:self selector:@ selector(timerFireMethod:)userInfo:nil repeats:YES];
[[NSRunLoop current unloop]add timer:my timer for mode:NSRunLoopCommonModes];
}
-(void)timerFireMethod:(NSTimer *)timer {
NSLog(@“已激發”);
timedOut = YES
}
-(void)stream:(ns stream *)stream handle event:(ns stream event)stream event
{
開關(streamEvent)
{
案例NSStreamEventHasBytesAvailable:
//處理傳入的流數據。
if(stream = =[我的輸入流])
{
uint 8 _ t buf[1024];
無符號整數長度= 0;
len =[[mySes inputStream]read:buf maxLength:1024];
if(len) {
_ data =[[NSMutableData alloc]init];
[_ data append bytes:(const void *)buf length:len];
NSLog(@ "響應:%@ ",[_數據描述]);
response =[[APDUResponse alloc]initWithData:_ data];
responseReceived = YES
}否則{
NSLog(@“沒有緩沖區!”);
}
}
打破;
...//代碼不相關
}
}
所以理論上是NSTimer在設置壹個布爾值的時候會出手,然後handleEvent委托方法如果接收到的數據運行在單獨的線程上也會設置另壹個布爾值。在該方法中,我們小睡壹會兒,並在這些bool之壹被設置時停止循環。
我遇到的問題是我的timerFireMethod在‘加時’的情況下聲音越來越大。我的直覺是,我實際上沒有在壹個單獨的線程上正確地設置計時器。
有沒有人能看到以上要求在這裏如何更好的實現或者建議?
溶液1:
相反,它強加了壹個問題,即不適當的同步方法本質上是異步的,這使得您的方法sendCommandAndWaitForResponse是異步的。
可以將“流寫”任務包裝為異步操作/任務/方法。例如,您可能最終得到壹個具有以下接口的並發NSOperation子類:
typedef void(^datatostreamcopier_completion_t)(id結果);
@ interface DataToStreamCopier:ns operation
- (id) initWithData:(NSData*)源數據
目標流:(NSOutputStream*)目標流
completion:(DataToStreamCopier _ completion _ t)completion handler;
@ property(nonatomic)NSThread * worker thread;
@property (nonatomic,copy)ns string * runLoopMode;
@property (atomic,readonly)long long totalBytesCopied;
//n操作
- (void)開始;
-(作廢)取消;
@property(非原子,只讀)布爾值已取消。
@property(非原子,只讀)BOOL isExecuting
@property (nonatomic,readonly) BOOL已完成。
@end
使用cancel方法可以實現“超時”功能。
您的方法sendCommandAndWaitForResponse:成為異步完成處理程序:
- (void)sendCommand:(NSData *)請求
completion:(DataToStreamCopier _ completion _ t)completion handler
{
DataToStreamCopier * op =[DataToStreamCopier initWithData:request
目標流:self.outputStream
completion:completion handler];
[op開始];
//設置超時帶塊:^{[op cancel];}
...
}
用法:
[自助發送命令:請求completion:^(id結果){
if([result is kindof class[NSError error]]){
NSLog(@“錯誤:%@”,錯誤);
}
否則{
//如果需要,在某個執行上下文(主線程)上執行:
dispatch _ async(dispatch _ get _ main _ queue(),^{
APDUResponse * response = result
...
});
}
}];
警告:
不幸的是,執行正常使用的並發NSOperation子類和雇傭壹個運行循環的基本任務並不像它應該的那樣簡單。將會有微妙的並發問題,迫使您使用同步原語(如鎖或調度隊列)和其他幾種技術來使它真正可靠。
幸運的是,基本上任何帶有NSOperation子類的運行周期任務都需要相同的“boiler board”代碼。所以,壹旦妳有了壹個通用的解決方案,編碼的工作量只是從“模板”中復制粘貼,然後根據妳的特定目的定制代碼。
替代解決方案:
嚴格地說,妳甚至不需要壹個子類,
NSOperation如果妳不打算放入這些任務,NSOperationQueue。您只需將它發送到分錄並發工序開始處。
方法-不需要NSOperationQueue。然後,您可以使您自己的實現更簡單,而無需使用該類的子類NSOperation,因為子類
n操作本身有其微妙之處。
然而,您實際上需要換行。妳在循環中運行“操作對象”NSStream對象,因為執行需要保留狀態,不能用簡單的異步方法完成。
因此,您可以使用任何自定義類,並且您可以看到存在作為異步操作的start和cancel方法以及通知調用站點完成基本任務的機制。
還有比完成處理程序更強大的方法來通知調用站點。例如:承諾或未來(參見維基文章未來和承諾)。
假設作為壹種手段,比如通知調用站點,來實現妳自己的“異步操作”類的承諾:
@ interface WriteDataToStreamOperation:async operation
- (void)開始;
-(作廢)取消;
@property(非原子,只讀)布爾值已取消。
@property(非原子,只讀)BOOL isExecuting
@property (nonatomic,readonly) BOOL已完成。
@property(非原子,只讀)Promise * promise
@end
妳原來的問題會更“同步”——即使是在異步的窗臺上:
您的sendCommand方法將變成:
註意:假設承諾類的壹些實現:
-(Promise *)send command:(ns data *)命令{
WriteDataToStreamOperation* op =
[[WriteDataToStreamOperation alloc]initWithData:command
output stream:self . output stream];
[op開始];
Promise*無極= op.promise
【承諾設置超時:100】;//100秒後超時
回報承諾;
}
註意:已經為承諾設置了“超時”。這基本上註冊了壹個定時器和壹個處理器。如果先前承諾的底層任務獲得了解決觸發計時器的承諾,計時器塊將解決超時錯誤。怎麽
(並且如果)這個實現依賴於承諾庫。(這裏我假設是RXPromise庫,我是作者。其他實現也可以實現這個功能)。
用法:
[自我發送命令:請求]。then(^id(APDUResponse*回應){
//對響應做壹些事情
...
返回...;//返回處理程序的結果
},
^id(NSError*error) {
//流錯誤或超時錯誤
NSLog(@“錯誤:%@”,錯誤);
返回nil//不返回任何內容
});
替代用法:
您可以用不同的方式設置超時。現在,假設我們沒有在超時時間內設置sendCommand:方法。
我們可以設置“外部”超時:
promise * promise =[self send command:request];
【承諾設置超時:100】;
promise.then(^id(APDUResponse*回應){
//對響應做壹些事情
...
返回...;//返回處理程序的結果
},
^id(NSError*error) {
//流錯誤或超時錯誤
NSLog(@“錯誤:%@”,錯誤);
返回nil//不返回任何內容
});
同步異步方法
通常,妳不需要也不應該在應用程序代碼中“轉換”異步方法的幾個同步方法。這總是導致次優和低效的代碼不必要地消耗系統資源,線程也是如此。
但是,您可能希望在有意義的單元測試中這樣做:
單元測試中異步“同步”方法的壹個例子
當測試您的實現時,您經常需要“等待”(或同步)結果。事實上,妳的基本任務實際上是在壹個循環中運行,可能是在同壹個線程中等待結果,這不會使解決方案更容易。
但是,您可以使用runLoopWait方法使用RXPromise庫輕松地完成這壹點,該方法有效地進入運行循環,並且沒有要解決的承諾:
-(void)testsendingcommandshouldreturnresponsebeforetimeout 10 {
promise * promise =[self send command:request];
【承諾設置超時:10】;
[promise.then(^id(apduresponse*的回應]
//對響應做壹些事情
XCTAssertNotNil(響應);
返回...;//返回處理程序的結果
},
^id(NSError*error) {
//流錯誤或超時錯誤
XCTestFail(@ "失敗,錯誤:%@ ",錯誤);
返回nil//不返回任何內容
})runLoopWait];//“等待”在運行循環中
}
在這裏,runLoopWait方法將進入壹個正在運行的循環,並等待提交因超時錯誤而被解析,或者等待底層任務解析了提交。承諾不會在沒有輪詢的情況下阻塞主線程和循環運行。當承諾完成時,它會讓循環繼續運行。其他正在運行的循環事件將照常處理。
註意:您可以安全地從主線程調用testsendingcommandshouldreturnresponsebeforetime 10,而無需停止它。這是絕對必要的,因為您的流委托方法可能在主線程上執行太多!
還有其他壹些方法通常在單元測試庫中找到,在那裏類似的函數被提供給異步方法或操作,進入運行循環的結果是“等待”。
不建議使用其他方法來異步方法或操作的最終結果是“等待”。這些方法通常被分派給私有線程,然後被阻塞,直到結果可用。
有用的資源
像類壹樣操作,使用promise(上面的點)代碼片段將壹個流復制到另壹個流:RXStreamToStreamCopier。