Objective-C 在后台任务完成后实现调用完成处理程序的方法时如何确定线程安全?

In Objective-C when implementing a method that calls a completion handler after a background task is finished how to determine thread safety?

我最近修改了一个现有的项目,修改了一些方法从返回值(BOOL)到调用完成块。 我遵循了这个非常好的答案:

现在我有了调用完成块的方法,它工作得很好,但我担心它会莫名其妙地失败 不是线程安全的。

我有我的区块声明:

typedef void(^myCompletion)(id, BOOL);

以下是我使用完成处理程序的方法:

-(void)myMethodThatTakesAnArgument:(id)object completionHandler:(myCompletion)completionblock {
    //do things that are allowed in the main thread:
    //...
    //...
    dispatch_queue_t backgroundQueue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_async(backgroundQueue, ^{

        [self doWorkButReturnImmediately];
        BOOL workIsNotFinished = [self isJobFinished];

        NSDate *processingStart = [NSDate date];
        NSTimeInterval timeElapsed = 0.0;
        while (workIsNotFinished && timeElapsed < 15.0) {
            [NSThread sleepForTimeInterval:1.0];
            timeElapsed = [[NSDate date] timeIntervalSinceDate:processingStart];
            workIsNotFinished = [self isJobFinished];
        }

      dispatch_async(dispatch_get_main_queue(), ^{
          // Return Result
          completionblock(object,YES);
      });
  });   
}

以下是我的调用方法:

- (void)callerMethod {

    NSArray *arrayOfObjects = [self prepareArrayOfObjects];

    NSMutableArray *unprocessedObjects = [arrayOfObjects mutableCopy];

    for (NSString *string in arrayOfObjects) {

        [self myMethod:string completionblock:^(id obj, BOOL success) {
        
        [unprocessedObjects removeObject:string];
        if(success){
            // do something with obj
            NSLog(@"success");
        }
        
        if ([unprocessedObjects count] == 0) {
        
            dispatch_async(dispatch_get_main_queue(), ^{
            
                // Everything is done, act accordingly.
            
            });
        
            }
        
        }
  }
}

我怀疑这种情况可能会以某种方式失败,我正在考虑添加一些线程安全代码。我不是这个主题的很多专家,但在我看来,@synchronized 可能是可行的方法。所以我想在一些@synchronized语句中嵌入被调用方法的代码,但我不确定在这种情况下是否有必要,如果是,我不确定具体把语句放在哪里,我可能考虑在后台队列中调度的部分。在代码中,我包含了一个在完成处理程序中传回的简单对象,它可以充当传递给@synchronized 的对象。任何帮助是极大的赞赏。谢谢

唯一让我突然想到的线程安全问题是 isJobFinished。您从第二个线程重复调用它,因此它的实现必须是线程安全的。

但是,这不是检查完成的方法。请记住这条规则:轮询错误。信号量好。

这是一个更简洁、更高效的实现,避免了 isJobFinished:

的任何问题
{
    dispatch_semaphore_t jobFinishedSemaphore = dispatch_semaphore_create(0);
    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    
    dispatch_async(backgroundQueue, ^{
        [self doWorkSignalingWhenDone:jobFinishedSemaphore];
        
        if ( dispatch_semaphore_wait(jobFinishedSemaphore, dispatch_time(DISPATCH_TIME_NOW, 15*NSEC_PER_SEC)) ) {
            // work timed out
        } else {
            // work successfully completed
            dispatch_async(dispatch_get_main_queue(), ^{
                // Return Result
                completionblock(self,YES);
            });
        }
    });
}

- (void)doWorkSignalingWhenDone:(dispatch_semaphore_t)finishedSemaphore
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        // do some work in the background
        // ...
        // signal work is finished
        dispatch_semaphore_signal(finishedSemaphore);
    });
}

如果修改 doWorkButReturnImmediately 不切实际,但 isJobFinished 是可观察到的,您可以使用值观察器做类似的事情。在观察者方法中,当 isJobFinished 的值从 NO 变为 YES 时发出信号量。

jobFinishedSemaphore 传递给观察者方法的最佳解决方案是使其成为对象的实例变量。但是,这意味着您一次只能 运行 这些任务之一。如果必须同时处理多个作业,或者您 不能 编辑 class 中的变量,这应该可行:

{
    dispatch_semaphore_t jobFinishedSemaphore = dispatch_semaphore_create(0);
    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

    [self addObserver:self
           forKeyPath:@"isJobFinished"
              options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld)
              context:(__bridge void * _Nullable)(jobFinishedSemaphore)];   /* WARNING! not retained */

    dispatch_async(backgroundQueue, ^{
        [self doWorkButReturnImmediately];
        
        if ( dispatch_semaphore_wait(jobFinishedSemaphore, dispatch_time(DISPATCH_TIME_NOW, 15*NSEC_PER_SEC)) ) {
            // work timed out
        } else {
            // work successfully completed
            dispatch_async(dispatch_get_main_queue(), ^{
                // Return Result
                completionblock(object,YES);
            });
        }
        
        // Note: this is "mostly" thread safe because this block still retains a reference to
        //  jobFinishedSemaphore, so that semephore has not been destroyed yet.
        // Remove the value observer before the reference to jobFinishedSemaphore goes out of scope.
        [self removeObserver:self forKeyPath:@"isJobFinished" context:(__bridge void * _Nullable)(jobFinishedSemaphore)];
        // at this point jobFinishedSemaphore goes out of scope, but the observer has been removed so it
        //  should no longer get sent with the (now invalid void*) reference to jobFinishedSemaphore.
    });
}

- (void)observeValueForKeyPath:(NSString*)keyPath
                      ofObject:(id)object
                        change:(NSDictionary*)change
                       context:(void*)context
{
    if ([keyPath isEqualToString:@"isJobFinished"])
        {
        if (   ![change[NSKeyValueChangeOldKey] boolValue]  // value was NO
            && [change[NSKeyValueChangeNewKey] boolValue] ) // value now YES
            {
            dispatch_semaphore_signal((__bridge dispatch_semaphore_t _Nonnull)(context));
            }
        }
}

此代码的可怕部分是您将信号量指针的非保留副本传递给观察者。但是只要你在信号量被销毁之前移除观察者,你应该没问题。