기금넷 공식사이트 - 금 선물 - SendRequest 의 시간 초과 현상을 어떻게 해결합니까?

SendRequest 의 시간 초과 현상을 어떻게 해결합니까?

입력 스트림과 출력 스트림을 위한 Bluetooth 연결 액세서리가 있습니다.

다음 목표를 달성하고 싶습니다.

OutputStream 에 데이터를 쓰고 10 초까지 다음 데이터를 받거나 보낼 때까지 기다립니다. 후속 데이터가 다른 반환 데이터에 도달하면 0 을 반환합니다.

저는 이렇게 하려고 했습니다.

-(apdu response *) sendcommandanwaitforresponse: (nsdata *) 요청 {

APDUResponse * 결과 :

만약 (! 장치 버스 & amp& amp 요청! = 0) {

DeviceIsBusy = YES

TimedOut = NO

ResponseReceived = 아니요 :

If ([[myses outputstream] hasspaceavailable]) {

[nsthread detachnewthreadselector: @ selector (starttimeout) to target: self with object: nil];

[[mySes output stream]write:[ 요청 바이트 ]maxLength:[ 요청 길이]];

그리고 (! 시간 출력 & amp& amp! 수신된 응답) {

수면 (2);

NSLog(@ "체크");

}

If(response received & amp;; & amp 응답! = 0) {

결과 = 응답

응답 = 0;

}

[내 타이머가 유효하지 않습니다];

MyTimer = nil

}

}

DeviceIsBusy = NO

결과를 반환합니다.

}

-(void) startTimeout {

NSLog(@ "시작 시간 초과");

Mytimer = [n timer timerwithtimeinterval:10.0 target: self selector: @ selector (timerfiremethor)

[[nsr unloop current unloop] addtimer: 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 = =[ 내 입력 흐름])

{

Uint8 _ tbuf [1024];

부호 없는 정수 길이 = 0;

Len = [[mysesinputstream] read: buf maxlength:1024];

If(len) {

_ data = [[nsmutabledata alloc] init];

[_ data append bytes: (constvoid *) buf length: len];

NSLog(@ "응답:% @", [_ 데이터 설명]);

Response = [[apdu response alloc] initwithdata: _ data];

ResponseReceived = YES

} 그렇지 않으면 {

NSLog(@ "버퍼 없음!" );

}

}

깨뜨리다

..//코드가 관련이 없습니다

}

}

따라서 이론적으로 NSTimer 는 부울 값을 설정할 때 손을 대고, handleEvent 위임 메서드는 수신된 데이터가 별도의 스레드에서 실행되는 경우 또 다른 부울 값을 설정합니다. 이 방법에서는, 우리는 잠시 낮잠을 자고, 이 bool 중 하나가 설정되었을 때 순환을 멈춘다.

내가 직면한 문제는 나의 timerFireMethod 가' 시간 초과' 상황에서 목소리가 점점 커지고 있다는 것이다. 제 직감은 제가 실제로 별도의 스레드에 타이머를 제대로 설정하지 않았다는 것입니다.

위의 요구 사항이 여기서 어떻게 더 잘 실현되거나 제안되는지 볼 수 있는 사람이 있습니까?

용액 1:

대신 부적절한 동기화 방법이 본질적으로 비동기적이라는 문제가 부과되어 sendCommandAndWaitForResponse 방법이 비동기적입니다.

Streams write 작업을 비동기 작업/작업/메소드로 래핑할 수 있습니다. 예를 들어, 다음과 같은 인터페이스를 가진 동시 NSOperation 하위 클래스를 얻을 수 있습니다.

Typedefvoid (datatostreamcopier _ completion _ t) (id 결과);

@ interfacedatatostreamcopier: nsoperation

-(id) initWithData:(NSData*) 소스 데이터

목표 흐름: (NSOutputStream*) 목표 흐름

Completion: (datatostreamcopier _ completion _ t) completion handler;

@ property (nonatomic) nsthread * 작업자 스레드;

@property (nonatomic, copy)ns string * runLoopMode;;

@property (atomic, readonly) long long total bytes copied;

//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 시작];

//시간 초과 밴드 블록 설정: {[opcancel]; }

...

}

사용법:

[self-service 명령: completion 요청: (id 결과) {

If ([result is kind of class [ns error error]]) {

NSLog(@ "오류:% @", 오류);

}

그렇지 않으면 {

//필요한 경우 실행 컨텍스트 (마스터 스레드) 에서 다음을 수행합니다.

Dispatch _ async (dispatch _ get _ main _ queue (), {

APDUResponse * response = result

...

});

}

}];

경고:

불행히도 정상적인 동시 NSOperation 하위 클래스를 수행하고 루프를 실행하는 기본 작업을 고용하는 것은 그렇게 간단하지 않습니다. 잠금 또는 일정 대기열과 같은 동기화 프리미티브와 기타 여러 기술을 사용하여 신뢰성을 높일 수 있는 미묘한 동시 문제가 발생할 수 있습니다.

다행히도 기본적으로 NSOperation 하위 클래스가 있는 런타임 작업에는 동일한 "boiler board" 코드가 필요합니다. 따라서 공통 솔루션이 있으면 인코딩된 작업량은 "템플릿" 에서 복사하여 붙여 넣은 다음 특정 목적에 맞게 코드를 사용자 정의할 수 있습니다.

대체 솔루션:

엄밀히 말하면, 하위 클래스도 필요하지 않습니다.

NSOperation 만약 당신이 이러한 작업을 넣지 않을 계획이라면, NSOperationQueue. 입력 동시작업 공정의 시작 부분으로 보내기만 하면 됩니다.

방법-NSOperationQueue 가 필요하지 않습니다. 그런 다음 해당 클래스의 하위 클래스인 NSOperation 을 사용하지 않고도 자체 구현을 쉽게 할 수 있습니다. 하위 클래스는 다음과 같습니다

N 조작 자체에는 미묘한 점이 있다.

그러나 실제로는 줄 바꿈이 필요합니다. 루프에서 "피연산자" NSStream 개체를 실행합니다. 실행은 보존 상태가 필요하기 때문에 간단한 비동기 방식으로 수행할 수 없습니다.

따라서 모든 사용자 정의 클래스를 사용할 수 있으며, 비동기 작업인 start 및 cancel 메서드와 호출 사이트에 기본 작업을 수행하도록 알리는 메커니즘이 있음을 알 수 있습니다.

핸들러를 완료하는 것보다 더 강력한 방법으로 호출 사이트에 알릴 수 있습니다. 예: 약속이나 미래 (위키 문장 미래와 약속 참조)

자신의 "비동기 작업" 클래스 약속을 달성하기 위해 호출 사이트에 통지하는 것과 같은 수단으로 가정합시다.

@ interfacewritedatatostreamoperation: async operation

-(void) 시작

-(무효화) 취소

@property (비원자, 읽기 전용) 부울 값이 취소되었습니다.

@property (비원자, 읽기 전용) BOOL isExecuting

@property (nonatomic, readonly) BOOL 이 완료되었습니다.

@property (비원자, 읽기 전용) Promise * promise

@end

당신의 원래 문제는 비동기 창턱에서도 더욱 "동기화" 될 것입니다.

SendCommand 메서드는 다음과 같이 변경됩니다.

참고: 약속 클래스의 일부 구현을 가정합니다.

-(promise *) sendcommand: (nsdata *) 명령 {

WriteDataToStreamOperation* op =

[[writedatatostreamoperation alloc] initwithdata: command

Outputstream: self.outputstream];

[op 시작];

Promise* 약속 없음 = op.promise

[약속 설정 제한 시간:100]; // 100 초 후 시간 초과

보상 약속

}

주의: 약속에 대해 시간 초과가 설정되었습니다. 이것은 기본적으로 타이머와 프로세서를 등록했다. 이전에 약속한 기본 작업이 트리거 타이머를 해결하겠다는 약속을 받은 경우 타이머 블록은 시간 초과 오류를 해결합니다. 어떻게

그리고 만약 이 실현이 약속고에 달려 있다면. (여기서 저는 RXPromise 라이브러리라고 가정합니다. 저는 저자입니다. 다른 구현도 이 기능을 구현할 수 있습니다.)

사용법:

[자체 전송 명령: 요청]. Then (id (apdu response * 응답) {

//응답에 대해 뭔가를 하다

...

반환 ...; //처리기 결과를 반환합니다

},

Id (^id(NSError*error) {

//스트림 오류 또는 시간 초과 오류

NSLog(@ "오류:% @", 오류);

Nil// 반환//아무 것도 반환하지 않습니다

});

대체 사용법:

시간 초과는 여러 가지 방법으로 설정할 수 있습니다. 이제 시간 초과 내에 sendCommand: 메서드를 설정하지 않았다고 가정합니다.

외부 시간 초과를 설정할 수 있습니다.

Promise * promise = [self send command: request];

[약속 설정 제한 시간:100];

Promise.then (id (apdu response * 응답) {

//응답에 대해 뭔가를 하다

...

반환 ...; //처리기 결과를 반환합니다

},

Id (^id(NSError*error) {

//스트림 오류 또는 시간 초과 오류

NSLog(@ "오류:% @", 오류);

Nil// 반환//아무 것도 반환하지 않습니다

});

동기식 비동기식 방법

일반적으로 응용 프로그램 코드에서 비동기 방법을 "변환" 하는 몇 가지 동기화 방법은 필요하지 않거나 해서는 안 됩니다. 이로 인해 항상 차선책 및 비효율적인 코드가 시스템 리소스를 불필요하게 소모하게 됩니다. 스레드도 마찬가지입니다.

그러나 의미 있는 단위 테스트에서 다음을 수행할 수 있습니다.

단위 테스트에서 비동기 "동기화" 방법의 예

구현을 테스트할 때 "대기" (또는 동기화) 결과가 필요한 경우가 많습니다. 사실, 기본 작업은 실제로 하나의 루프에서 실행되며, 같은 스레드에서 결과를 기다리는 것일 수 있습니다. 이로 인해 솔루션이 더 쉬워지지 않을 수 있습니다. (데이비드 아셀, Northern Exposure (미국 TV 드라마), 성공명언)

그러나 runLoopWait 메서드를 사용하면 RXPromise 라이브러리를 사용하여 쉽게 이 작업을 수행할 수 있습니다. 이 방법은 실행 주기에 효율적으로 진입하며 해결할 약속이 없습니다.

-(void) testsendingcommandshouldreturnresponsebeforetimeout10 {

Promise * promise = [self send command: request];

[약속 설정 제한 시간:10];

[promise.then (id (apdu response * 의 응답)

//응답에 대해 뭔가를 하다

XCTAssertNotNil (응답);

반환 ...; //처리기 결과를 반환합니다

},

Id (^id(NSError*error) {

//스트림 오류 또는 시간 초과 오류

XCTestFail(@ "실패, 오류:% @", 오류);

Nil// 반환//아무 것도 반환하지 않습니다

}) runloopwait]; //"대기 중" 루프 실행 중

}

여기서 runLoopWait 메서드는 실행 중인 루프로 들어가 시간 초과 오류로 인해 제출이 확인될 때까지 기다리거나 기본 작업이 제출을 확인할 때까지 기다립니다. 폴링 없이 주 스레드와 루프 실행을 차단하지 않겠다고 약속합니다. 약속이 완료되면 루프가 계속 실행됩니다. 실행 중인 다른 루프 이벤트는 평소와 같이 처리됩니다.

참고: 주 스레드에서 testsendingcommandshouldreturnresponsebeforetime10 을 중지하지 않고 안전하게 호출할 수 있습니다. 스트림 위임 방법이 주 스레드에서 너무 많이 실행될 수 있기 때문에 절대적으로 필요합니다!

또 다른 방법은 일반적으로 단위 테스트 라이브러리에서 찾을 수 있습니다. 여기서 유사한 함수가 비동기 메서드 또는 작업에 제공되고 실행 루프에 들어간 결과는 "대기" 입니다.

비동기 방식 또는 작업에 다른 방법을 사용하지 않는 것이 좋습니다. 최종 결과는 "대기" 입니다. 이러한 방법은 일반적으로 개인 스레드에 배정된 다음 결과를 사용할 수 있을 때까지 차단됩니다.

유용한 자원

클래스와 마찬가지로 promise (위 점) 코드 조각을 사용하여 한 스트림을 다른 스트림인 RXStreamToStreamCopier 로 복사합니다.