【问题标题】:NSOperation wait until asynchronous block executesNSOperation 等待异步块执行
【发布时间】:2015-01-09 11:24:59
【问题描述】:

我需要将异步操作放入操作队列中,但是它们需要在另一个之后执行

self.operationQueue = [NSOperationQueue new];
self.operationQueue.maxConcurrentOperationCount = 1;

[self.operationQueue addOperationWithBlock:^{

    // this is asynchronous
    [peripheral1 connectWithCompletion:^(NSError *error) {

    }];

}];

[self.operationQueue addOperationWithBlock:^{

    // this is asynchronous
    [peripheral2 connectWithCompletion:^(NSError *error) {

    }];

}];

问题是,由于 peripheralN connectWithCompletion 是异步的,队列中的操作结束并执行下一个,但是我需要模拟,peripheralN connectWithCompletion 是同步的并等待操作结束,直到异步块执行

所以我需要这样的行为,只使用操作队列

    [peripheral1 connectWithCompletion:^(NSError *error) {

            [peripheral2 connectWithCompletion:^(NSError *error) {

            }];

    }];

【问题讨论】:

    标签: cocoa asynchronous nsoperation nsoperationqueue


    【解决方案1】:

    基于solution of @mllm,通过使用setSuspended:,我终于能够按顺序运行异步HTTP GET 请求的FOR 循环。 服务器不允许并发连接并在请求泛滥时返回错误。

    下面的解决方案解决了这个问题,因为下一个NSOperation 仅在上一个操作完成后才开始:

    @property (nonatomic) NSOperationQueue *myQueue;
    
    - (void)requestVersions {
        // Create NSOperationQueue for serial retrieval:
        _myQueue = [[NSOperationQueue alloc] init];
        _myQueue.maxConcurrentOperationCount = 1;
        
        // Parse array:
        for (NSObject *object in _array) {
            // Add block operation:
            [_myQueue addOperationWithBlock:^{
                // Suspend next execution until request is completed:
                [_myQueue setSuspended:YES];
                
                // Request version async:
                [self getDetailsOfObjectWithId:object.identifier
                             completionHandler:^(NSString * _Nullable version) {
                    NSLog(@"Version = %@", version);
                    
                    NSLog(@"_myQueue.operationCount = %lu", (unsigned long) _myQueue.operationCount);
                    
                    // When operations are pending, start the next:
                    if (_myQueue.operationCount > 0) {
                        [_myQueue setSuspended:NO];
                    }
                    else {
                        // Queue is complete
                        NSLog(@"All %lu versions have been requested.", (unsigned long)[_array count]);
                    }
                }];
            }];
        }
    }
    

    请注意,完成处理程序中使用operationCount 来了解所有操作何时完成(而不是 Key Value Observer)。

    控制台日志显示请求数组中 12 个对象的版本。队列一一执行,就是想要的结果:

    2021-04-10 15:02:01.911996+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.001955+0200  Version = 4.1.10
    2021-04-10 15:02:02.002091+0200  _myQueue.operationCount = 11
    2021-04-10 15:02:02.002292+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.108418+0200  Version = 1.0.18
    2021-04-10 15:02:02.108611+0200  _myQueue.operationCount = 10
    2021-04-10 15:02:02.108844+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.201625+0200  Version = 0.0.85
    2021-04-10 15:02:02.201810+0200  _myQueue.operationCount = 9
    2021-04-10 15:02:02.202048+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.289626+0200  Version = 3.1.0
    2021-04-10 15:02:02.289851+0200  _myQueue.operationCount = 8
    2021-04-10 15:02:02.290140+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.369086+0200  Version = 2.0.2
    2021-04-10 15:02:02.369295+0200  _myQueue.operationCount = 7
    2021-04-10 15:02:02.369525+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.444134+0200  Version = 1.0.11
    2021-04-10 15:02:02.444270+0200  _myQueue.operationCount = 6
    2021-04-10 15:02:02.444386+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.513550+0200  Version = 4.0.0
    2021-04-10 15:02:02.513741+0200  _myQueue.operationCount = 5
    2021-04-10 15:02:02.513952+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.600841+0200  Version = 1.2.4
    2021-04-10 15:02:02.601030+0200  _myQueue.operationCount = 4
    2021-04-10 15:02:02.601243+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.691918+0200  Version = 7.0.2
    2021-04-10 15:02:02.692064+0200  _myQueue.operationCount = 3
    2021-04-10 15:02:02.692242+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.777012+0200  Version = 3.1.81
    2021-04-10 15:02:02.777116+0200  _myQueue.operationCount = 2
    2021-04-10 15:02:02.777244+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.864673+0200  Version = 1.0.12
    2021-04-10 15:02:02.864851+0200  _myQueue.operationCount = 1
    2021-04-10 15:02:02.865050+0200  getDetailsOfObjectWithId:completionHandler:
    2021-04-10 15:02:02.961894+0200  Version = 1.0.7
    2021-04-10 15:02:02.962073+0200  _myQueue.operationCount = 0
    2021-04-10 15:02:02.962226+0200  All 12 versions have been requested.
    

    【讨论】:

      【解决方案2】:

      我所做的是分别在之前和之后玩[myQueue setSuspended:YES][myQueue setSuspended:NO]

      例如:

      [myQueue addOperationWithBlock:^{
          [myQueue setSuspended:YES];
          [someBackendService doSomeAsyncJobWithCompletionBlock:^{
              callback(YES, nil);
              [myQueue setSuspended:NO];
          });
      }];
      

      实现的效果是队列在异步任务之前被挂起,因此即使返回了操作块,也只有在异步任务的完成块被调用时才开始下一个操作(当然以maxConcurrentOperationCount为准)。

      【讨论】:

      • 这是一个很好的解决方案。我只是想感谢你的努力。谢谢!
      • 感谢您提供这个出色的解决方案。我在下面发表了一条评论,展示了我如何在 FOR 循环中实现这一点,并使用 operationCount 知道所有操作何时完成(而不是 Key Value Observer)。
      【解决方案3】:

      NSBlockOperation 无法处理异步操作,但创建一个可以...的NSOperation 子类并不是那么难...

      基本上,您需要创建一个NSOperation,它接受一个将另一个 块作为完成处理程序的块。块可以这样定义:

      typedef void(^AsyncBlock)(dispatch_block_t completionHandler);
      

      然后,在您的NSOperation 子类的start 方法中,您需要调用您的AsyncBlock,并传递给它一个dispatch_block_t,它将在执行完成时调用。您还需要确保KVO 符合NSOperationisFinishedisExecuting 属性(至少)(参见NSOperation 文档中的KVO-Compliant Properties);这就是允许NSOperationQueue 等待异步操作完成的原因。

      类似这样的:

      - (void)start {
          [self willChangeValueForKey:@"isExecuting"];
          _executing = YES;
          [self didChangeValueForKey:@"isExecuting"];
      
          self.block(^{
              [self willChangeValueForKey:@"isExecuting"];
              _executing = NO;
              [self didChangeValueForKey:@"isExecuting"];
              [self willChangeValueForKey:@"isFinished"];
              _finished = YES;
              [self didChangeValueForKey:@"isFinished"];
          });
      }
      

      请注意,_executing_finished 需要在您的子类中的某个位置进行定义,并且您需要覆盖 isExecutingisFinished 属性以返回正确的值。

      如果你把所有这些放在一起,连同一个接受你的 AsyncBlock 的初始化程序,那么你可以像这样将你的操作添加到队列中:

      [self.operationQueue addOperationWithBlock:^(dispatch_block_t completionHandler) {
          [peripheral1 connectWithCompletion:^(NSError *error) {
              // call completionHandler when the operation is done
              completionHandler();
          }];
      }];
      
      [self.operationQueue addOperationWithBlock:^(dispatch_block_t completionHandler) {
          [peripheral2 connectWithCompletion:^(NSError *error) {
              // call completionHandler when the operation is done
              completionHandler();
          }];
      }];
      

      我在这里整理了一个简单版本的要点:AsyncOperationBlock。 这只是最低限度的实现,但它应该可以工作(例如,如果 isCancelled 也实现了,那就太好了)。

      为了完整起见,复制到这里:

      AsyncBlockOperation.h:

      #import <Foundation/Foundation.h>
      
      typedef void(^AsyncBlock)(dispatch_block_t completionHandler);
      
      @interface AsyncBlockOperation : NSOperation
      
      @property (nonatomic, readonly, copy) AsyncBlock block;
      
      + (instancetype)asyncBlockOperationWithBlock:(AsyncBlock)block;
      
      - (instancetype)initWithAsyncBlock:(AsyncBlock)block;
      
      @end
      
      
      @interface NSOperationQueue (AsyncBlockOperation)
      
      - (void)addAsyncOperationWithBlock:(AsyncBlock)block;
      
      @end
      

      AsyncBlockOperation.m:

      #import "AsyncBlockOperation.h"
      
      @interface AsyncBlockOperation () {
          BOOL _finished;
          BOOL _executing;
      }
      
      @property (nonatomic, copy) AsyncBlock block;
      
      @end
      
      
      @implementation AsyncBlockOperation
      
      + (instancetype)asyncBlockOperationWithBlock:(AsyncBlock)block {
          return [[AsyncBlockOperation alloc] initWithAsyncBlock:block];
      }
      
      - (instancetype)initWithAsyncBlock:(AsyncBlock)block {
          if (self = [super init]) {
              self.block = block;
          }
          return self;
      }
      
      - (void)start {
          [self willChangeValueForKey:@"isExecuting"];
          _executing = YES;
          [self didChangeValueForKey:@"isExecuting"];
      
          self.block(^{
              [self willChangeValueForKey:@"isExecuting"];
              _executing = NO;
              [self didChangeValueForKey:@"isExecuting"];
              [self willChangeValueForKey:@"isFinished"];
              _finished = YES;
              [self didChangeValueForKey:@"isFinished"];
          });
      }
      
      - (BOOL)isFinished {
          return _finished;
      }
      
      - (BOOL)isExecuting {
          return _executing;
      }
      
      - (BOOL)isAsynchronous {
          return YES;
      }
      
      @end
      
      @implementation NSOperationQueue (AsyncBlockOperation)
      
      - (void)addAsyncOperationWithBlock:(AsyncBlock)block {
          [self addOperation:[AsyncBlockOperation asyncBlockOperationWithBlock:block]];
      }
      
      @end
      

      【讨论】:

      • 它不会与appstore中断吗?因为 _executing 是私有 API?
      • 对不起,我不太清楚那部分..._executing_finished 在这种情况下是你自己的 ivars。我将完整的实现添加到答案中,以便您了解它是如何工作的。
      • @MikeS 在如何使用的示例中,您应该将 addOperationWithBlock 替换为 addAsyncOperationWithBlock (可能您在后期编辑中更改了函数名称)
      • 真的很酷!通过这种方式,我能够达到我需要的结果!谢谢你!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-04
      • 1970-01-01
      • 1970-01-01
      • 2022-09-30
      • 1970-01-01
      • 2013-10-12
      相关资源
      最近更新 更多