Concurrency and Asynchronism in (NS)Operation

Jean-David Morgenstern-P
4 min readFeb 7, 2020

--

What concurrency and asynchronism are. How to use them correctly in the context of Operation and OperationQueue.

I find Apple’s API and documentation on (NS)Operation confusing. It uses the notions of synchronism and concurrency interchangeably. In this article, we will define what synchronism and concurrency are and understand how to use them on Operation instances. We will outline an implementation of an asynchronous Operation subclass. By the end of our read, we will be able to design a set of operations, synchronous or asynchronous, that run concurrently in a synchronous or asynchronous way with respect to the current thread.

Operation (previously NSOperation) and OperationQueue are Foundation’s highest level of abstractions for managing concurrency. An operation instance encapsulates data and logic to run a single task. Operation and Operation Queues are particularly useful when we need to schedule a series of tasks in some order. They enable us to add dependencies to operations and to prioritise them in a declarative syntax. We won’t detail basic usages of Operation’s methods and properties. Great tutorials on the subject have already been published. Instead let’s turn our focus on two of Operation’s main features: asynchronism and concurrency.

Asynchronism

By default, operation instances are synchronous with respect to the current thread. Apple writes:

When you call the start() method of a synchronous operation directly from your code, the operation executes immediately in the current thread. By the time the start() method of such an object returns control to the caller, the task itself is complete.

Synchronous operations are operations whose state is finished when the start() method returns.

Asynchronous operations are operations whose state is undetermined when the start() method returns.

Let us design first a synchronous operation. At the very least, we should override the main() method. This method is called by the start() method:

Synchronous Operation

The synchronous operation guarantees that its task has completed before we reach the next line of code that outputs “Done”.

Let us now design an asynchronous operation, where the start() method may return control to the caller before its task has completed. One way to do that is by dispatching a block of code to another thread.

Asynchronous Operation

The asynchronous operation offers no guarantee that its task has completed before we reach the next line of code. Our code can produce the same output than the earlier synchronous example or it may first print “Done” and only then “Executing”.

A synchronous operation can still be executed asynchronously with respect to the current thread. For instance, this happens when you use an operation queue. In that case, you delegate to the queue the responsibilty to decide when to call the start method. The SDK makes sure that this call happens from a different thread.

Synchronous Operation added to an Operation Queue

To sum up, there are two ways to make an operation executes asynchronously with respect to the current thread. You can manually start an asynchronous operation or you can dispatch a synchronous (or asynchronous) operation to a different thread.

Ambiguous Asynchronism

I find Apple’s use of the adjective synchronous in the context of Operation confusing. First there is a discrepancy between the new and the old documentation. The new documentation compares asynchronous vs synchronous operations, as quoted above. The old documentation refers to concurrent versus non-concurrent operations. In the same vein, the new API encourages us to use the isAsynchronous property instead of the old isConcurrent property.

Synchronism has nothing to do with concurrency or multi-threading but is concerned with dependency. Two tasks are asynchronous if their order of execution is of no consequence. The amazing fact is that Operation includes a clear API for synchronism:

Dependencies are a convenient way to execute operations in a specific order. You can add and remove dependencies for an operation using the addDependency(_:) and removeDependency(_:) methods.

How are we to understand an asynchronous operation in the language of Apple? It depends on the other side of the equation. If we look at the current thread, then an asynchronous operation is an operation whose undetermined state would have no effect on the next line of code. To introduce synchronism between two operations, we should add one as a dependency of the other.

Designing an Asynchronous Operation

Synchronous operations only need to override their main() method and their state is managed out of the box. This is no longer the case for asynchronous operations. Asynchronous operations are responsible for updating their state, overriding the start() method and both isExecuting and isFinished properties in a thread safe and KVO compliant way.

Asynchronous Operation, thread safe and KVO compliant

Concurrency

Two tasks are said to run concurrently when the second task starts before the first task finishes. This happens when the tasks are executed on different threads.

When a task completes before another task starts, we say that the tasks are executed serially. Here is a serial execution of a two operations.

Serial execution of two operations

To introduce concurrency, we can use the dedicated OperationQueue.

Concurrent execution of two operations

Operation queues manage a pool of thread upon which they execute operations concurrently. Without specifying dependencies, the order in which operations finish is undetermined.

When Asynchronism meet Concurrency

We noted above that both synchronous and asynchronous operations can be executed asynchronously with respect to the current thread. Operations queues add the possibility to run a set of operations concurrently in asynchronous as well as in synchronous way. To block the current thread, you can use the OperationQueue method addOperations(_ ops: [Operation], waitUntilFinished wait: Bool):

Synchronous while Concurrent execution of two operations

Let us wrap up with a question: How would you make sure two operations, the first one being asynchronous while the second one is synchronous, run serially and return control to the current thread in a synchronous manner?

--

--