I want to achieve the following goals:
Write data into the outputStream and wait until the next data is received or sent, until 10 second. If subsequent data reaches other returned data, zero is returned.
I tried to do this:
-(apduresponse *) sendcommandanwaitforresponse: (nsdata *) request {
APDUResponse * results;
If (! Device bus&& request! = zero) {
deviceIsBusy = YES
timedOut = NO
ResponseReceived = no;
if([[my ses output stream]hasSpaceAvailable]){
[NSThread detachNewThreadSelector:@ selector(start time out)to target:self with object:nil];
[[mySes output stream]write:[ request byte ]maxLength:[ request length]];
And (! Time output & & Received response) (
Sleep (2);
NSLog(@ "tick");
}
if(response received & amp; & respond! = zero) {
Result = response;
Response = zero;
}
[My timer is invalid];
myTimer = nil
}
}
deviceIsBusy = NO
Return the result;
}
- (void) startTimeout {
NSLog(@ "startup timeout");
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(@ "fired");
timedOut = YES
}
-(void)stream:(ns stream *)stream handle event:(ns stream event)stream event
{
Switch (streamEvent)
{
Case NSStreamEventHasBytesAvailable:
//Process incoming stream data.
If(stream = =[ my input stream])
{
uint 8 _ t buf[ 1024];
Unsigned integer length = 0;
len =[[mySes inputStream]read:buf maxLength: 1024];
if(len) {
_ data =[[NSMutableData alloc]init];
[_ data append bytes:(const void *)buf length:len];
NSLog(@ "Response:% @", [_ data description]);
response =[[APDUResponse alloc]initWithData:_ data];
responseReceived = YES
} Otherwise {
NSLog(@ "No buffer!" );
}
}
Break;
...//The code is irrelevant.
}
}
Therefore, in theory, NSTimer will take action when setting a Boolean value, and then the handleEvent delegate method will also set another Boolean value if the received data runs on a separate thread. In this method, we take a nap and stop the loop when one of these bool is set.
The problem I have is that my timerFireMethod is getting louder and louder in' overtime'. My intuition is that I didn't actually set the timer correctly on a separate thread.
Can anyone see how the above requirements can be better realized or suggested here?
Solution 1:
On the contrary, it imposes a problem that improper synchronization methods are asynchronous in nature, which makes your method sendCommandAndWaitForResponse asynchronous.
You can wrap the Streaming Write task as an asynchronous operation/task/method. For example, you may end up with a subclass of concurrent NSOperation with the following interfaces:
Typedef void (datatostreamcopier _ completion _ t) (ID result);
@ interface DataToStreamCopier:ns operation
-(id) initWithData:(NSData*) source data
Target stream: (NSOutputStream*) target stream
completion:(DataToStreamCopier _ completion _ t)completion handler;
@ property(nonatomic)NSThread * worker thread;
@property (nonatomic,copy)ns string * runLoopMode;
@property (atomic,readonly)long long totalBytesCopied;
//n operation
-(void) start;
-(void) cancel;
The @property (non-atomic, read-only) Boolean value has been cancelled.
@property (non-atomic, read-only) BOOL isExecuting
@property (nonatomic, readonly) BOOL has been completed.
@end
Use the cancel method to realize the "timeout" function.
Your method sendCommandAndWaitForResponse: Become an asynchronous completion handler:
-(void)sendCommand:(NSData *) request
completion:(DataToStreamCopier _ completion _ t)completion handler
{
DataToStreamCopier * op =[DataToStreamCopier initWithData:request
Target stream: self.outputStream
completion:completion handler];
[op start];
//Set timeout block: {[opcancel]; }
...
}
Usage:
[Self-service sending command: request completion:^(id result) {
if([result is kindof class[NSError error]]){
NSLog(@ "Error:% @", error);
}
Otherwise {
//If necessary, execute on an execution context (main thread):
dispatch _ async(dispatch _ get _ main _ queue(),^{
APDUResponse * response = result
...
});
}
}];
Warning:
Unfortunately, the basic tasks of executing a normal concurrent NSOperation subclass and hiring a running loop are not as simple as it should be. There will be subtle concurrency problems, forcing you to use synchronization primitives (such as locks or scheduling queues) and several other technologies to make it truly reliable.
Fortunately, basically any runtime task with a subclass of NSOperation needs the same "boiler board" code. So, once you have a general solution, the workload of coding is just to copy and paste from the "template" and then customize the code according to your specific purpose.
Alternative solution:
Strictly speaking, you don't even need a subclass,
NSOperation you don't plan to put these tasks in, NSOperationQueue. You only need to send it to the beginning of the journal concurrent operation.
Method-NSOperationQueue is not required. Then, you can make your own implementation simpler without using NSOperation, a subclass of this class, because the subclass
N operation itself has its subtleties.
However, you actually need to wrap lines. You run the "operation object" NSStream object in a loop, because the execution needs to preserve the state and cannot be completed by a simple asynchronous method.
Therefore, you can use any custom class, and you can see that there are start and cancel methods as asynchronous operations and a mechanism to inform the calling site to complete basic tasks.
There is a more powerful way to notify the calling site than to complete the handler. For example: promise or future (see wiki article Future and promise).
Suppose as a means, such as notifying the calling site, to realize the promise of your own "asynchronous operation" class:
@ interface WriteDataToStreamOperation:async operation
-(void) start;
-(void) cancel;
The @property (non-atomic, read-only) Boolean value has been cancelled.
@property (non-atomic, read-only) BOOL isExecuting
@property (nonatomic, readonly) BOOL has been completed.
@property (non-atomic, read-only) Promise * promise
@end
Your original question will be more "synchronous"-even on the asynchronous windowsill:
Your sendCommand method will become:
Note: Suppose some implementations of the commitment class:
-(promise *) sendcommand: (nsdata *) command {
WriteDataToStreamOperation* op =
[[WriteDataToStreamOperation alloc]initWithData:command
output stream:self . output stream];
[op start];
Promise* promise = op.promise
[Commitment setting timeout:100]; ///kloc-timeout after 0/00 seconds.
Commitment to return;
}
Note: Timeout has been set for the commitment. This basically registers a timer and a processor. If the previously promised underlying task gets a promise to solve the trigger timer, the timer block will solve the timeout error. how
(and if) this implementation depends on the commitment library. (I assume that it is the RXPromise library here, and I am the author. Other implementations can also achieve this function).
Usage:
[Self-sending command: request]. Then (id (apdu response * response) {
//Do something about the response
...
Return ...; //Returns the result of the handler
},
^id(NSError*error) {
//Stream error or timeout error
NSLog(@ "Error:% @", error);
Return nil// Nothing is returned.
});
Alternative usage:
You can set the timeout in different ways. Now, suppose we didn't set the sendCommand: method within the timeout.
We can set an "external" timeout:
promise * promise =[self send command:request];
[Commitment setting timeout:100];
Promise. then (id (apdu response * response) {
//Do something about the response
...
Return ...; //Returns the result of the handler
},
^id(NSError*error) {
//Stream error or timeout error
NSLog(@ "Error:% @", error);
Return nil// Nothing is returned.
});
Synchronous asynchronous method
Usually, you don't need and shouldn't "convert" several synchronous methods of asynchronous methods in application code. This always leads to suboptimal and inefficient code unnecessarily consuming system resources, as do threads.
However, you may want to do this in meaningful unit tests:
An example of asynchronous "synchronization" method in unit testing
When testing your implementation, you often need to "wait" (or synchronize) the results. In fact, your basic task is actually running in a loop, probably waiting for the result in the same thread, which will not make the solution easier.
However, you can easily do this using the RXPromise library by using the runLoopWait method, which effectively enters the running loop and has no commitments to solve:
-(void)testsendingcommandshouldreturnresponsebeforetimeout 10 {
promise * promise =[self send command:request];
[Commitment setting timeout:10];
[promise. then (id (apdu response * response)]
//Do something about the response
XCTAssertNotNil (response);
Return ...; //Returns the result of the handler
},
^id(NSError*error) {
//Stream error or timeout error
XCTestFail(@ "failed, error:% @", error);
Return nil// Nothing is returned.
})runLoopWait]; //"Wait" is in the running loop
}
At this point, the runLoopWait method will enter a running loop and wait for the submission to be resolved due to a timeout error, or wait for the underlying task to resolve the submission. Promise not to block the main thread and loop running without polling. When the commitment is completed, it will keep the loop running. Other running loop events will be handled as usual.
Note: You can safely call TestSendingCommandShouldDreturnResponse for Time10 from the main thread without stopping it. This is absolutely necessary because your stream delegate method may execute too much on the main thread!
Other methods are usually found in the unit test library, where similar functions are provided to asynchronous methods or operations, and the result of entering the running loop is "waiting".
It is not recommended to use other methods to asynchronous methods or operations, and the end result is "waiting". These methods are usually assigned to private threads and then blocked until the results are available.
Useful resources
Operate like a class, and copy one stream to another stream using the promise (point above) code snippet: RXStreamToStreamCopier.