【问题标题】:Asynchronous request to the server from background thread从后台线程到服务器的异步请求
【发布时间】:2009-11-13 11:21:19
【问题描述】:

当我尝试从后台线程向服务器执行异步请求时遇到了问题。我从来没有得到这些请求的结果。显示问题的简单示例:

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

一切正常。我假设后台线程在异步请求完成之前就死了,这会导致问题,但我不确定。我对这个假设是否正确?有什么办法可以避免这个问题?

我知道我可以使用同步请求来避免这个问题,但这只是一个简单的例子,实际情况更复杂。

提前致谢。

【问题讨论】:

    标签: objective-c iphone nsurlconnection


    【解决方案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 GuideRunLoop Reference 中了解有关运行循环的更多信息。

    【讨论】:

    • 在研究了几个小时(8+)关于如何在 NSInvocationOperation 中执行异步 NSURLConnection 之后,没有一个解决方案像 CFRunLoopRun() 和 CFRunLoopStop() 那样优雅。这比使用 KVO 协议处理“isExecuting”实例变量要好得多。需要注意的一点是,我使用 @selector(performSelectorOnMainThread:withObject:waitUntilDone:modes:) 传递 [NSArray arrayWithObject:NSDefaultRunLoopMode] 作为模式。主线程满足 UIKit 要求,NSDefaultRunLoopMode 保持 UI 交互流畅。非常感谢!
    • 对于使用 ARC 编译的项目,NSAutoReleasePool 不可用,因此代码看起来像 @autoreleasepool { [self downloadImage:urlString]; CFRunLoopRun(); }
    • 太好了,谢谢!
    • 如果我想停止下载,我们可以从主线程中停止这个后台线程吗?
    • 根据 Apple 线程编程指南,“如果您使用线程执行一些长时间运行的预定任务,您可能可以避免启动运行循环。”
    【解决方案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
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-15
        • 1970-01-01
        • 1970-01-01
        • 2012-09-15
        • 1970-01-01
        相关资源
        最近更新 更多