【问题标题】:Hold NSOperationQueue until previous operation completes保持 NSOperationQueue 直到前一个操作完成
【发布时间】:2014-09-16 00:22:00
【问题描述】:

我想执行一些操作,并且只需要在完成前一个操作后开始下一个操作。我添加的操作将向服务器发送异步调用并接收数据。我只想在第一次调用服务器完成从服务器接收数据后开始下一个操作。该怎么做?

{.... 
     PhotoDownloader *pd = [[PhotoDownloader alloc] init];
     [GetGlobalOperationQueue addOperation:pd]; 
}

在 PhotoDownloader 中,我将分配所需的参数并调用处理所有请求的全局函数

[GlobalCommunicationUtil sendServerReq:reqObj withResponseHandler:self];

在 sendServerReq 方法中,我将构造 URL 请求并将其发送到服务器,此调用是“sendAsynchronousRequest”调用。 PhotoDownloader 将具有 CommunicationUtil 的委托方法。

【问题讨论】:

  • 您能告诉我们您当前如何将请求发送到服务器吗?

标签: ios objective-c nsoperationqueue


【解决方案1】:

这个问题分为两部分:

  1. 你问:

    如何让一个操作在前一个操作完成之前不开始?

    要做到这一点,理论上,您可以简单地创建一个串行队列(如果您想让 所有 操作等到前一个操作完成,这很好)。使用NSOperationQueue,您只需将maxConcurrentOperationCount 设置为1 即可实现。

    或者,更灵活一点,您可以在需要依赖关系的操作之间建立依赖关系,但在其他方面享受并发性。例如,如果您想根据第三个网络请求的完成发出两个网络请求,您可以执行以下操作:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 4;   // generally with network requests, you don't want to exceed 4 or 5 concurrent operations;
                                             // it doesn't matter too much here, since there are only 3 operations, but don't
                                             // try to run more than 4 or 5 network requests at the same time
    
    NSOperation *operation1 = [[NetworkOperation alloc] initWithRequest:request1 completionHandler:^(NSData *data, NSError *error) {
        [self doSomethingWithData:data fromRequest:request1 error:error];
    }];
    
    NSOperation *operation2 = [[NetworkOperation alloc] initWithRequest:request2 completionHandler:^(NSData *data, NSError *error) {
        [self doSomethingWithData:data fromRequest:request2 error:error];
    }];
    
    NSOperation *operation3 = [[NetworkOperation alloc] initWithRequest:request3 completionHandler:^(NSData *data, NSError *error) {
        [self doSomethingWithData:data fromRequest:request3 error:error];
    }];
    
    [operation2 addDependency:operation1];   // don't start operation2 or 3 until operation1 is done
    [operation3 addDependency:operation1];
    
    [queue addOperation:operation1];         // now add all three to the queue
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    
  2. 你问:

    如何确保在发出的异步网络请求也完成之前,操作不会完成?

    同样,这里有不同的方法。有时您可以利用信号量使异步过程同步。但是,更好的是使用并发的NSOperation 子类。

    “异步”NSOperation 只是在发出isFinished 通知之前不会完成(从而允许它启动的任何异步任务完成)。而NSOperation 类只需在其isAsynchronous 实现中返回YES 即可将其自身指定为异步操作。因此,异步操作的抽象类实现可能如下所示:

    //  AsynchronousOperation.h
    
    @import Foundation;
    
    @interface AsynchronousOperation : NSOperation
    
    /**
     Complete the asynchronous operation.
    
     If you create an asynchronous operation, you _must_ call this for all paths of execution
     or else the operation will not terminate (and dependent operations and/or available 
     concurrent threads for the operation queue (`maxConcurrentOperationCount`) will be blocked.
     */
    - (void)completeOperation;
    
    @end
    

    //
    //  AsynchronousOperation.m
    //
    
    #import "AsynchronousOperation.h"
    
    @interface AsynchronousOperation ()
    
    @property (getter = isFinished, readwrite)  BOOL finished;
    @property (getter = isExecuting, readwrite) BOOL executing;
    
    @end
    
    @implementation AsynchronousOperation
    
    @synthesize finished  = _finished;
    @synthesize executing = _executing;
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            _finished  = NO;
            _executing = NO;
        }
        return self;
    }
    
    - (void)start {
        if (self.isCancelled) {
            if (!self.isFinished) self.finished = YES;
            return;
        }
    
        self.executing = YES;
    
        [self main];
    }
    
    - (void)completeOperation {
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished  = YES;
    }
    
    #pragma mark - NSOperation methods
    
    - (BOOL)isAsynchronous {
        return YES;
    }
    
    - (BOOL)isExecuting {
        @synchronized(self) { return _executing; }
    }
    
    - (BOOL)isFinished {
        @synchronized(self) { return _finished; }
    }
    
    - (void)setExecuting:(BOOL)executing {
        [self willChangeValueForKey:@"isExecuting"];
        @synchronized(self) { _executing = executing; }
        [self didChangeValueForKey:@"isExecuting"];
    }
    
    - (void)setFinished:(BOOL)finished {
        [self willChangeValueForKey:@"isFinished"];
        @synchronized(self) { _finished = finished; }
        [self didChangeValueForKey:@"isFinished"];
    }
    
    @end
    

    现在我们有了抽象的异步NSOperation 子类,我们可以在具体的NetworkOperation 类中使用它:

    #import "AsynchronousOperation.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    typedef void(^NetworkOperationCompletionBlock)(NSData * _Nullable data, NSError * _Nullable error);
    
    @interface NetworkOperation : AsynchronousOperation
    
    @property (nullable, nonatomic, copy) NetworkOperationCompletionBlock networkOperationCompletionBlock;
    @property (nonatomic, copy) NSURLRequest *request;
    
    - (instancetype)initWithRequest:(NSURLRequest *)request completionHandler:(NetworkOperationCompletionBlock)completionHandler;
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    //  NetworkOperation.m
    
    #import "NetworkOperation.h"
    
    @interface NetworkOperation ()
    
    @property (nonatomic, weak) NSURLSessionTask *task;
    
    @end
    
    
    @implementation NetworkOperation
    
    - (instancetype)initWithRequest:(NSURLRequest *)request completionHandler:(NetworkOperationCompletionBlock)completionHandler {
        self = [self init];
    
        if (self) {
            self.request = request;
            self.networkOperationCompletionBlock = completionHandler;
        }
    
        return self;
    }
    
    - (void)main {
        NSURLSession *session = [NSURLSession sharedSession];
    
        NSURLSessionTask *task = [session dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (self.networkOperationCompletionBlock) {
                self.networkOperationCompletionBlock(data, error);
                self.networkOperationCompletionBlock = nil;
            }
    
            [self completeOperation];
        }];
    
        [task resume];
    
        self.task = task;
    }
    
    - (void)cancel {
        [super cancel];
    
        [self.task cancel];
    }
    
    @end
    

    现在,在这个例子中,我使用这些异步网络请求的基于块的实现,但这个想法同样适用于基于委托的连接/会话。 (唯一麻烦的是NSURLSession 将其与任务相关的委托方法指定为会话的一部分,而不是网络任务。)

    显然,您自己的NetworkOperation 类的实现可能会有很大的不同(使用委托模式或完成块模式等),但希望这能说明并发操作的想法。有关详细信息,请参阅 并发编程指南Operation Queues 章节,特别是标题为“为并发执行配置操作”的部分。

【讨论】:

  • 嗨,您的第二个解释回答了我的问题。是的,我正在使用“sendAsynchronousRequest”,如果我从带有参数的操作中调用 global/util 方法并且该 global/util 方法正在发送异步请求并且它已经有,我对如何处理有疑问一个完成块。我已经用另一种方式做到了。我将操作队列作为属性分配给操作本身,第一行将暂停操作队列。一旦异步请求完成并在操作中调用委托,我将恢复操作队列。效果如我所愿,有什么缺点吗?
  • 您绝对不想暂停队列或类似的事情(因为您可能(最终)有多个请求同时运行)。我可能会在这个全局 util 方法中重复完成块模式(参见 stackoverflow.com/a/21736595/1271826 的第一部分),然后在提供给这个全局 util 方法的完成处理程序中提供 completeOperation。或者你可以只使用sendSynchronousRequest,整个问题就消失了(但请确保你永远不要从主队列中调用该方法)。
  • 如果您需要更多帮助,请使用相关代码示例更新您的问题。
  • 我已经包含了代码 sn-p。你现在可以指导我吗?
  • @Arock Inside sendServerReq,你说你正在使用sendAsynchronousRequest。但是,从您的代码 sn-p 中不清楚,sendServerReq 如何通知调用者下载已完成。你有一个withResponseHandler,但是:我是否推断你有一些协议,sendServerReq 将通过该协议通知responseHandler 下载完成?因此,您将有 main(在我的代码 sn-p 中)调用 sendServerReq,但在完成请求的委托方法中将调用 completeOperation
【解决方案2】:

异步操作的快速版本(不是很明显):

final class NetworkOperation: Operation {

lazy var session: NSURLSession = {
        return NSURLSession.sharedSession()
}()

private var _finished = false {

    willSet {
        willChangeValue(forKey: "isFinished")
    }

    didSet {
        didChangeValue(forKey: "isFinished")
    }

}



private var _executing = false {

    willSet {
        willChangeValue(forKey: "isExecuting")
    }

    didSet {
        didChangeValue(forKey: "isExecuting")
    }

}


override var isAsynchronous: Bool {
    return true
}


override var isFinished: Bool {
    return _finished
}


override var isExecuting: Bool {
    return _executing
}

override func start() {
    _executing = true
    execute()
}


func execute() {
    task = session.downloadTaskWithURL(NSURL(string: "yourURL")!) {
        (url, response, error) in

        if error == nil {

            // Notify the response by means of a closure or what you prefer
            // Remember to run in the main thread since NSURLSession runs its
            // task on background by default

        } else {

            // Notify the failure by means of a closure or what you prefer
            // Remember to run in the main thread since NSURLSession runs its
            // task on background by default

        }

        // Remember to tell the operation queue that the execution has completed
        self.finish()
    }

}

func finish() {
    //Async task complete and hence the operation is complete
    _executing = false
    _finished = true
}


}

序列化操作:

let operationQueue = OperationQueue()

let operation1 = NetworkOperation()
let operation2 = NetworkOperation()

operation2.addDependency(operation1)

operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)

【讨论】:

    【解决方案3】:

    使用 NSOperationQueue 是强制性的吗?

    这种行为很容易用串行队列实现

    https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

    假设你有一个类来管理操作,你会在你的 init 方法中创建一个串行调度队列

    queue = dispatch_queue_create("com.example.MyQueue", NULL);
    

    你会有一个方法来排队请求,像这样

    - (void) enqueueRequest:(NSURL *)requestURL
    {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(queue, ^{ /* get data from requestURL */ }) });
    }
    

    这样,一次只有一个请求处于活动状态,即使每个请求都将在单独的后台线程中执行,并且多个请求将被排入队列,直到活动请求完成。

    【讨论】:

    • 是的,为了简单起见,网络请求将是同步的,dispatch_async 调用将负责启动一个新线程以避免阻塞主线程
    猜你喜欢
    • 1970-01-01
    • 2015-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多