并发队列问题非并发 NSOperations

发布于 2024-08-09 11:05:36 字数 3263 浏览 2 评论 0原文

我有一个 NSOperationQueue,其中包含 2 个 NSOperations,并设置为通过将 setMaxConcurrentOperationCount 设置为 1 来依次执行它们。

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

问题是我发现并发操作只有先添加到队列中才有效。如果它发生在任何非并发操作之后,那么奇怪的是 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

排队代码

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

I have an NSOperationQueue which contains 2 NSOperations and is set to perform them one after another by setting setMaxConcurrentOperationCount to 1.

One of the operations is a standard non-concurrent operation (just a main method) which synchronously retrieves some data from the web (on the separate operation thread of course). The other operation is a concurrent operation as I need to use some code which has to run asynchronously.

The problem is that I have discovered that the concurrent operation only works if it is added to the queue first. If it comes after any non-concurrent operations, then strangely the start method gets called fine, but after that method ends and I have setup my connection to callback a method, it never does. No further operations in the queue get executed after. It's as if it hangs after the start method returns, and no callbacks from any url connections get called!

If my concurrent operation is put first in the queue then it all works fine, the async callbacks work and the subsequent operation executes after it has completed. I don't understand at all!

You can see the test code for my concurrent NSOperation below, and I'm pretty sure it's solid.

Any help would be greatly appreciated!

Main Thread Observation:

I have just discovered that if the concurrent operation is first on the queue then the [start] method gets called on the main thread. However, if it is not first on the queue (if it is after either a concurrent or non-concurrent) then the [start] method is not called on the main thread. This seems important as it fits the pattern of my problem. What could be the reason for this?

Concurrent NSOperation Code:

@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

Queuing Code

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

苏璃陌 2024-08-16 11:05:36

我已经发现问题所在了!

Dave Dribin 的这两篇无价文章详细描述了并发操作,以及 Snow Leopard 和 Java 中存在的问题。 iPhone SDK 在异步调用需要运行循环的事物时引入了这一点。

http://www.dribin.org/dave/blog/档案/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

}

I've discovered what the problem was!

These two priceless articles by Dave Dribin describe concurrent operations in great detail, as well as the problems that Snow Leopard & the iPhone SDK introduce when calling things asynchronously that require a run loop.

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/

Thanks to Chris Suter too for pointing me in the right direction!

The crux of it is to ensure that the start method us called on the main thread:

- (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

}
日记撕了你也走了 2024-08-16 11:05:36

您的问题很可能与 NSURLConnection 有关。 NSURLConnection 依赖于运行某种模式的运行循环(通常只是默认模式)。

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

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

  2. 创建并管理您自己的线程。这不是一个好的解决方案。

  3. 调用 -[NSURLConnection ScheduleInRunLoop:forMode:] 并传入主线程或您了解的另一个线程。如果这样做,您可能需要首先调用 -[NSURLConnection unscheduleInRunLoop:forMode:] ,否则您可能会在多个线程中接收数据(或者至少文档似乎是这么建议的)。

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

    使用类似

  5. #4 的变体:使用 +[NSURLConnection sendSynchronousRequest:returningResponse:error:]。

如果你能逃脱惩罚,请执行#4 或#5。

Your problem is most likely with NSURLConnection. NSURLConnection depends on a run loop running a certain mode (usually just the default ones).

There are a number of solutions to your problem:

  1. Make sure that this operation only runs on the main thread. If you were doing this on OS X, you’d want to check that it does what you want in all run loop modes (e.g. modal and event tracking modes), but I don’t know what the deal is on the iPhone.

  2. Create and manage your own thread. Not a nice solution.

  3. Call -[NSURLConnection scheduleInRunLoop:forMode:] and pass in the main thread or another thread that you know about. If you do this, you probably want to call -[NSURLConnection unscheduleInRunLoop:forMode:] first otherwise you could be receiving the data in multiple threads (or at least that’s what the documentation seems to suggest).

  4. Use something like +[NSData dataWithContentsOfURL:options:error:] and that will also simplify your operation since you can make it a non-concurrent operation instead.

  5. Variant on #4: use +[NSURLConnection sendSynchronousRequest:returningResponse:error:].

If you can get away with it, do #4 or #5.

硪扪都還晓 2024-08-16 11:05:36

我没有注意到,也没有看到任何提及 addDependency: 的内容,这似乎是让操作按正确顺序执行的先决条件。

简而言之,第二个操作取决于第一个操作。

I didn't notice, nor do I see any mention of addDependency:, which would seem like a prerequisite to getting operations to execute in the proper order.

In short, the second operation depends on the first.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文