web-dev-qa-db-ja.com

バックグラウンドスレッドからサーバーへの非同期リクエスト

バックグラウンドスレッドからサーバーへの非同期リクエストを実行しようとすると、問題が発生します。私はそれらの要求の結果を得たことがありません。問題を示す簡単な例:

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

すべてが正しく機能しています。非同期リクエストが終了する前にバックグラウンドスレッドが停止し、これが問題の原因になると思いますが、よくわかりません。私はこの仮定で正しいですか?この問題を回避する方法はありますか?

同期要求を使用してこの問題を回避できることはわかっていますが、これは単純な例であり、実際の状況はより複雑です。

前もって感謝します。

37
Dmytro

はい、スレッドは終了しています。これは、次を追加することで確認できます。

-(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リファレンス を参照してください。

59
nall

バックグラウンドスレッドで接続を開始できますが、デリゲートメソッドがメインスレッドで呼び出されていることを確認する必要があります。これはで行うことはできません

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

すぐに始まるので。

これを実行してデリゲートキューを構成すると、セカンダリスレッドでも機能します。

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

NSURLRequestsはとにかく完全に非同期です。メインスレッド以外のスレッドからNSURLRequestを作成する必要がある場合、これを行う最善の方法はmake NSURLRequestfromメインスレッド

// 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メインスレッドで実行されます。

したがって、HTTPget関数の最初の行は次のようになります。

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
}
0
bobobobo