Default Value of Maxconcurrentoperationcount for Nsoperationqueue

Default value of maxConcurrentOperationCount for NSOperationQueue

From the documentation,

The maximum number of concurrent operations set explicitly on the
receiver using the setMaxConcurrentOperationCount: method. If no value
has been explicitly set, this method returns
NSOperationQueueDefaultMaxConcurrentOperationCount by default
.

So it is NSOperationQueueDefaultMaxConcurrentOperationCount. If this is set, it will choose an appropriate value based on the number of available processors and other relevant factors.

This is how it is defined:

enum {
NSOperationQueueDefaultMaxConcurrentOperationCount = -1
};

NSOperationQueueDefaultMaxConcurrentOperationCount: The
default maximum number of operations is determined dynamically by the
NSOperationQueue object based on current system conditions.

NSOperation and NSOperationQueue with maxConcurrentOperationCount = 1

Based on how I read Apple's documentation, the concurrent property in NSOperation is readonly, and tells us if the operation will run asynchronously or not. If you plan to start operations manually, you need to make the your NSOperation return YES for asynchronous in order to avoid blocking the thread you are starting your operations from. The concurrent property is just used to monitor the state of the operation if you are running them manually

If you are adding your NSOperation to an NSOperationQueue, the queue will ignore the value of the asynchronousproperty, and run operations according to the maxConcurrentOperationCount.

So, to answer your question: If you run all your operations manually, and set asynchronous to YES, the number of operations running in parallel will depend on how big the delay is between each time you call start on your operations, and how long it will take to finish them. If you add them to a queue, your queue will run the operations one by one, as a serial queue.

NSOperationQueue: a sequence of NSOperation's with dependencies VS (maxConcurrentOperationCount == 1)?

To answer your questions:

  1. It's safe to combine this particular sequence of operations with the dependencies you have given with maxConcurrentOperations = 1.
  2. The queue will run op2, op3 and op1 or op2, op1, op3 if you reverse the dependency order of op1 and op2.

Theres nothing tricky in the dependency chain you've specified and NSOperationQueue can take care of things automatically. You can only really get into trouble if you specify a circular dependency (e.g op3 depends on op1), or you have an operation that isn't added to the queue, and so can't get executed to satisfy a dependency.

Apple has this to say about cancellation in the NSOperationQueue class reference:

Canceling an operation causes the operation to ignore any dependencies it may have. This behavior makes it possible for the queue to execute the operation’s start method as soon as possible. The start method, in turn, moves the operation to the finished state so that it can be removed from the queue.

All NSOperation subclasses should handle cancellation correctly by first checking to see if it has been cancelled and then immediately finish the operation without performing any actions. If this isn't done then it's a bug, and operations may execute even though they have been cancelled.

(Interestingly this also applies for NSBlockOperation, which I didn't realise. You explicitly need to check self.isCancelled in the block).

I used CodeRunner on the App Store to try this all out and modified your program slightly. It's reproduced below.

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {
@autoreleasepool {
NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1"); }];
NSOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op2"); }];
NSOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op3"); }];

[op3 addDependency:op2];
[op2 addDependency:op1];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;

[queue addOperations:@[op1, op2, op3] waitUntilFinished:YES];
}
}

For a NSBlockOperation to refer to itself you need to do this, which is a bit disgusting but looks better in a NSOperation subclass as you can refer to self.

__block NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1 cancelled=%d", op1.cancelled); }];

Setting maxConcurrentOperationCount in swift

If you check the header file for for NSOperationQueue:

var maxConcurrentOperationCount: Int

you'll see that maxConcurrentOperationCount is a property, not a method, so you'll need to use:

self.operationQueue = NSOperationQueue()
self.operationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount

OperationQueue with custom `maxConcurrentOperationCount` does not pick up / execute all operations in the queue, after finishing first operation

The issue is that the methods are not KVC/KVO compliant. As the Operation documentation says:

The NSOperation class is key-value coding (KVC) and key-value observing (KVO) compliant for several of its properties.

If you provide custom implementations for any of the preceding properties, your implementations must maintain KVC and KVO compliance.

Constraints on the degree of concurrency (e.g., both maxConcurrentOperationCount and addDependency(_:)) rely upon KVO to know when the prior operation is complete. If you fail to perform the required KVO notifications, the queue will not know when subsequent operations may proceed.

See the latter part of Trying to Understand Asynchronous Operation Subclass for an example implementation.


FWIW, here is an asynchronous operation implementation:

public class AsynchronousOperation: Operation {

@Atomic @objc private dynamic var state: OperationState = .ready

// MARK: - Various `Operation` properties

open override var isReady: Bool { state == .ready && super.isReady }
public final override var isExecuting: Bool { state == .executing }
public final override var isFinished: Bool { state == .finished }
public final override var isAsynchronous: Bool { true }

// KVO for dependent properties

open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
if [#keyPath(isReady), #keyPath(isFinished), #keyPath(isExecuting)].contains(key) {
return [#keyPath(state)]
}

return super.keyPathsForValuesAffectingValue(forKey: key)
}

// Start

public final override func start() {
if isCancelled {
state = .finished
return
}

state = .executing

main()
}

/// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.

open override func main() {
fatalError("Subclasses must implement `main`.")
}

/// Call this function to finish an operation that is currently executing

public final func finish() {
if !isFinished { state = .finished }
}
}

private extension AsynchronousOperation {
/// State for this operation.

@objc enum OperationState: Int {
case ready
case executing
case finished
}
}

With the following:

@propertyWrapper
public class Atomic<T> {
private var _wrappedValue: T
private var lock = NSLock()

public var wrappedValue: T {
get { lock.synchronized { _wrappedValue } }
set { lock.synchronized { _wrappedValue = newValue } }
}

public init(wrappedValue: T) {
_wrappedValue = wrappedValue
}
}

extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}

With the above, I abstract the asynchronous Operation code into something I can subclass and inherit the asynchronous behaviors. E.g., here is an operation that performs the same asyncAfter as your example (but with some extra OSLog signposts so I can visually see the operations in Instruments):

import os.log

private let log = OSLog(subsystem: "Op", category: .pointsOfInterest)

class MyOperation: AsynchronousOperation {
var value: Int

init(value: Int) {
self.value = value
super.init()
}

override func main() {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Operation", signpostID: id, "%d", value)

DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
finish()
os_signpost(.end, log: log, name: "Operation", signpostID: id, "%d", value)
}
}
}

Then ...

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1

for i in 0..<5 {
queue.addOperation(MyOperation(value: i))
}

... yields a timeline of the operations like so:

Sample Image



Related Topics



Leave a reply



Submit