Подтвердить что ты не робот

Асинхронный запрос к серверу из фонового потока

У меня возникла проблема, когда я пытался выполнять асинхронные запросы на сервер из фонового потока. Я никогда не получал результатов этих запросов. Простой пример, который показывает проблему:

@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]; 

 [self performSelector:@selector(downloadImage) withObject:nil]; 

все работает правильно. Я предполагаю, что фоновый поток умирает до запроса async, завершает работу, и это вызывает проблему, но я не уверен. Правильно ли я с этими предположениями? Есть ли способ избежать этой проблемы?

Я знаю, что могу использовать запрос синхронизации, чтобы избежать этой проблемы, но это просто простой пример, реальная ситуация сложнее.

Спасибо заранее.

4b9b3361

Ответ 1

Да, поток выходит. Вы можете увидеть это, добавив:

-(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());
}

Подробнее о циклах запуска в Руководство по Threading и Ссылка RunLoop.

Ответ 2

Вы можете запустить соединение в фоновом потоке, но вы должны обеспечить, чтобы методы делегата вызывались в основном потоке. Это невозможно сделать с помощью

[[NSURLConnection alloc] initWithRequest:urlRequest 
                                delegate:self];

так как он начинается немедленно.

Сделайте это, чтобы настроить очередь делегатов, и работает даже на вторичных потоках:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest 
                                                              delegate:self 
                                                      startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];

Ответ 3

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
}