Асинхронный запрос на сервер из фонового потока
у меня возникла проблема, когда я попытался выполнить асинхронные запросы к серверу из фонового потока. Я никогда не получал результатов от этих запросов. Простой пример, который показывает проблему:
@protocol AsyncImgRequestDelegate
-(void) imageDownloadDidFinish:(UIImage*) img;
@end
@interface AsyncImgRequest : NSObject
{
NSMutableData* receivedData;
id<AsyncImgRequestDelegate> delegate;
}
@property (nonatomic,retain) id<AsyncImgRequestDelegate> delegate;
-(void) downloadImage:(NSString*) url ;
@end
@implementation AsyncImgRequest
-(void) downloadImage:(NSString*) url
{
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:20.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
receivedData=[[NSMutableData data] retain];
} else {
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[delegate imageDownloadDidFinish:[UIImage imageWithData:receivedData]];
[connection release];
[receivedData release];
}
@end
затем я называю это из основного потока
asyncImgRequest = [[AsyncImgRequest alloc] init];
asyncImgRequest.delegate = self;
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
метод downloadImage приведен ниже:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[asyncImgRequest downloadImage:@"http://photography.nationalgeographic.com/staticfiles/NGS/Shared/StaticFiles/Photography/Images/POD/l/leopard-namibia-sw.jpg"];
[pool release];
}
проблема в том, что метод imageDownloadDidFinish никогда не вызывается. Более того ни один из методов
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
называются. Однако если я заменить
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
by
[self performSelector:@selector(downloadImage) withObject:nil];
все работает правильно. Я предполагаю, что фоновый поток умирает до завершения асинхронного запроса, и это вызывает проблему, но я не уверен. Прав ли я в своих предположениях? Есть ли способ избежать этой проблемы?
Я знаю, что могу использовать запрос синхронизации, чтобы избежать этой проблемы, но это просто простой пример, реальная ситуация сложнее.
спасибо заранее.
3 ответов
Да, поток выходит. Вы можете увидеть это, добавив:
-(void)threadDone:(NSNotification*)arg
{
NSLog(@"Thread exiting");
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(threadDone:)
name:NSThreadWillExitNotification
object:nil];
вы можете сохранить поток от выхода с помощью:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[self downloadImage:urlString];
CFRunLoopRun(); // Avoid thread exiting
[pool release];
}
однако это означает, что поток никогда не выйдет. Так что ты должен прекратить это, когда закончишь.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
Узнайте больше о циклах выполнения в Руководство По Резьбе и Ссылка На RunLoop.
вы можете запустить соединение в фоновом потоке, но вы должны убедиться, что методы делегата вызываются в основном потоке. Это невозможно сделать с
[[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self];
так как он сразу же начинается.
сделайте это, чтобы настроить очередь делегатов, и она работает даже на вторичных потоках:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
NSURLRequests полностью асинхронны в любом случае. Если вам нужно сделать NSURLRequest из потока, отличного от основного потока, я думаю, что лучший способ сделать это-просто сделать NSURLRequest
из основной нити.
// Code running on _not the main thread_:
[self performSelectorOnMainThread:@selector( SomeSelectorThatMakesNSURLRequest )
withObject:nil
waitUntilDone:FALSE] ; // DON'T block this thread until the selector completes.
все это делает, это отстреливать HTTP-запрос из основной нити (так что он действительно работает и не таинственно исчезает). Ответ HTTP вернется в обратные вызовы как обычный.
если вы хотите сделать это с GCD, вы можете просто пойти
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{ //
// Perform your HTTP request (this runs on the main thread)
} ) ;
на MAIN_QUEUE
выполняется в основном потоке.
Итак, первая строка моей функции HTTP get выглядит так:
void Server::get( string queryString, function<void (char*resp, int len) > onSuccess,
function<void (char*resp, int len) > onFail )
{
if( ![NSThread isMainThread] )
{
warning( "You are issuing an HTTP request on NOT the main thread. "
"This is a problem because if your thread exits too early, "
"I will be terminated and my delegates won't run" ) ;
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{
// Perform your HTTP request (this runs on the main thread)
get( queryString, onSuccess, onFail ) ; // re-issue the same HTTP request,
// but on the main thread.
} ) ;
return ;
}
// proceed with HTTP request normally
}