排队并发和非并发NSOperations的问题

Mic*_*all 5 iphone multithreading cocoa-touch objective-c nsoperation

我有一个NSOperationQueue,它包含2个NSOperations,并设置为通过设置setMaxConcurrentOperationCount为1 来一个接一个地执行它们.

其中一个操作是标准的非并发操作(只是一种main方法),它同步从Web(当然是在单独的操作线程上)检索一些数据.另一个操作是并发操作,因为我需要使用一些必须异步运行的代码.

问题是我发现并发操作只有在首先添加到队列中才有效.如果它出现在任何非并发操作之后,那么奇怪的是该start方法被称为正常,但在该方法结束并且我已经设置我的连接以回调方法之后,它永远不会.之后,队列中不再执行任何操作.就好像它在start方法返回后挂起,并且没有来自任何url连接的回调被调用!

如果我的并发操作首先放入队列中,那么一切正常,异步回调工作,后续操作在完成后执行.我根本不明白!

您可以在下面看到我的并发NSOperation的测试代码,我很确定它是可靠的.

任何帮助将不胜感激!

主线观察:

我刚刚发现,如果并发操作首先在队列中,那么该[start]方法将在主线程上调用.但是,如果它不是队列中的第一个(如果它在并发或非并发之后),[start]则不在主线程上调用该方法.这似乎很重要,因为它符合我的问题模式.这可能是什么原因?

并行的NSOperation代码:

@interface ConcurrentOperation : NSOperation {
    BOOL executing;
    BOOL finished;
}
- (void)beginOperation;
- (void)completeOperation;
@end

@implementation ConcurrentOperation
- (void)beginOperation {
    @try {

        // Test async request
        NSURLRequest *r = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.google.com"]];
        NSURLConnection *c = [[NSURLConnection alloc] initWithRequest:r delegate:self];
        [r release];

    } @catch(NSException * e) {
        // Do not rethrow exceptions.
    }
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"Finished loading... %@", connection);
    [self completeOperation];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"Finished with error... %@", error);
    [self completeOperation]; 
}
- (void)dealloc {
    [super dealloc];
}
- (id)init {
    if (self = [super init]) {

        // Set Flags
        executing = NO;
        finished = NO;

    }
    return self;
}
- (void)start {

    // Main thread? This seems to be an important point
    NSLog(@"%@ on main thread", ([NSThread isMainThread] ? @"Is" : @"Not"));

    // Check for cancellation
    if ([self isCancelled]) {
        [self completeOperation];
        return;
    }

    // Executing
    [self willChangeValueForKey:@"isExecuting"];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];

    // Begin
    [self beginOperation];

}

// Complete Operation and Mark as Finished
- (void)completeOperation {
    BOOL oldExecuting = executing;
    BOOL oldFinished = finished;
    if (oldExecuting) [self willChangeValueForKey:@"isExecuting"];
    if (!oldFinished) [self willChangeValueForKey:@"isFinished"];
    executing = NO;
    finished = YES;
    if (oldExecuting) [self didChangeValueForKey:@"isExecuting"];
    if (!oldFinished) [self didChangeValueForKey:@"isFinished"];
}

// Operation State
- (BOOL)isConcurrent { return YES; }
- (BOOL)isExecuting { return executing; }
- (BOOL)isFinished { return finished; }

@end
Run Code Online (Sandbox Code Playgroud)

排队代码

// Setup Queue
myQueue = [[NSOperationQueue alloc] init];
[myQueue setMaxConcurrentOperationCount:1];

// Non Concurrent Op
NonConcurrentOperation *op1 = [[NonConcurrentOperation alloc] init];
[myQueue addOperation:op1];
[op1 release];

// Concurrent Op
ConcurrentOperation *op2 = [[ConcurrentOperation alloc] init];
[myQueue addOperation:op2];
[op2 release];
Run Code Online (Sandbox Code Playgroud)

Mic*_*all 10

我发现了问题所在!

这两篇Dave Dribin的无价文章详细描述了并发操作,以及Snow Leopard和iPhone SDK在异步调用需要运行循环的东西时引入的问题.

http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/ http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/

感谢Chris Suter指出我正确的方向!

它的关键是确保start我们调用主线程的方法:

- (void)start {

    if (![NSThread isMainThread]) { // Dave Dribin is a legend!
        [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
        return;
    }

    [self willChangeValueForKey:@"isExecuting"];
    _isExecuting = YES;
    [self didChangeValueForKey:@"isExecuting"];

    // Start asynchronous API

}
Run Code Online (Sandbox Code Playgroud)


Chr*_*ter 6

你的问题最有可能是NSURLConnection.NSURLConnection依赖于运行某种模式的运行循环(通常只是默认模式).

您的问题有很多解决方案:

  1. 确保此操作仅在主线程上运行.如果您在OS X上执行此操作,您需要检查它是否在所有运行循环模式中执行您想要的操作(例如模态和事件跟踪模式),但我不知道iPhone上的交易是什么.

  2. 创建和管理自己的线程.不太好的解决方案.

  3. 调用 - [NSURLConnection scheduleInRunLoop:forMode:]并传入主线程或您知道的其他线程.如果你这样做,你可能想先调用 - [NSURLConnection unscheduleInRunLoop:forMode:],否则你可能会在多个线程中接收数据(或者至少是文档似乎建议的那样).

  4. 使用类似+ [NSData dataWithContentsOfURL:options:error:]的东西,这也将简化您的操作,因为您可以将其作为非并发操作.

  5. 变量#4:使用+ [NSURLConnection sendSynchronousRequest:returningResponse:error:].

如果你可以逃脱它,做#4或#5.