Проблемы в очереди параллельных и не параллельных операций NSO - PullRequest
5 голосов
/ 29 октября 2009

У меня есть NSOperationQueue, который содержит 2 NSOperations и настроен на их выполнение один за другим, установив setMaxConcurrentOperationCount в 1.

Одной из операций является стандартная не параллельная операция (просто метод main), которая синхронно извлекает некоторые данные из Интернета (разумеется, в отдельном потоке операций). Другая операция - это параллельная операция, так как мне нужно использовать некоторый код, который должен выполняться асинхронно.

Проблема в том, что я обнаружил, что параллельная операция работает, только если она добавлена ​​в очередь первой. Если это происходит после каких-либо непараллельных операций, то, как ни странно, метод start вызывается нормально, но после того, как этот метод заканчивается, и я настроил свое соединение для обратного вызова метода, он никогда этого не сделает. Никакие дальнейшие операции в очереди не выполняются после. Это как если бы оно зависало после возврата метода start, и никакие обратные вызовы из любых URL-соединений не вызывались!

Если моя параллельная операция помещается первой в очередь, то все работает нормально, асинхронные обратные вызовы работают, а последующая операция выполняется после ее завершения. Я совсем не понимаю!

Вы можете увидеть тестовый код для моей параллельной NSOperation ниже, и я почти уверен, что он твердый.

Любая помощь будет принята с благодарностью!

Наблюдение за главной нитью:

Я только что обнаружил, что если параллельная операция сначала выполняется в очереди, то метод [start] вызывается в главном потоке. Однако, если он не является первым в очереди (если он идет после одновременного или не одновременного), то метод [start] не вызывается в главном потоке. Это кажется важным, поскольку это соответствует образцу моей проблемы. Что может быть причиной этого?

Код одновременной работы NSO:

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

Ответы [ 3 ]

10 голосов
/ 29 октября 2009

Я обнаружил, в чем проблема!

В этих двух бесценных статьях Дэйва Дрибина подробно описываются параллельные операции, а также проблемы, которые 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/

Спасибо Крису Сутеру за то, что он указал мне правильное направление!

Суть в том, чтобы гарантировать, что метод 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

}
6 голосов
/ 29 октября 2009

Ваша проблема, скорее всего, связана с 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.

0 голосов
/ 29 октября 2009

Я не заметил и не вижу упоминаний о addDependency:, что может показаться необходимым условием для выполнения операций в правильном порядке.

Короче говоря, вторая операция зависит от первой.

...