NSOperationQueue addOperations waitUntilFinished - ios

Hi I am building an app using Swift. I need to process notifications in a specific order. Therefore I am trying to use addOperations waitUntilFinished.
Here is what I did:
let oldify = NSOperation()
oldify.completionBlock = {
println("oldify")
}
let appendify = NSOperation()
appendify.completionBlock = {
println("appendify")
}
let nettoyify = NSOperation()
nettoyify.completionBlock = {
println("nettoyify")
}
NSOperationQueue.mainQueue().maxConcurrentOperationCount = 1
NSOperationQueue.mainQueue().addOperations([oldify, appendify, nettoyify], waitUntilFinished: true)
With this code none of the operations is being executed. When I try this instead:
NSOperationQueue.mainQueue().maxConcurrentOperationCount = 1
NSOperationQueue.mainQueue().addOperation(oldify)
NSOperationQueue.mainQueue().addOperation(appendify)
NSOperationQueue.mainQueue().addOperation(nettoyify)
The operations get executed but not in the right order.
Does anyone know what I'm doing wrong? I am getting confident in swift but completely new to NSOperations

A couple of issues:
You are examining behavior of the completion block handlers. As the completionBlock documentation says:
The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context.
The queue will manage the operations themselves, but not their completion blocks (short of making sure that the the operation finishes before its completionBlock is started). So, bottom line, do not make any assumptions about (a) when completion blocks are run, (b) the relation of one operation's completionBlock to other operations or their completionBlock blocks, etc., nor (c) which thread they are performed on.
Operations are generally executed in the order in which they were added to the queue. If you add an array of operations, though, the documentation makes no formal assurances that they are enqueued in the order they appear in that array. You might, therefore, want to add the operations one at a time.
Having said that, the documentation goes on to warn us:
An operation queue executes its queued operation objects based on their priority and readiness. If all of the queued operation objects have the same priority and are ready to execute when they are put in the queue—that is, their isReady method returns YES—they are executed in the order in which they were submitted to the queue. However, you should never rely on queue semantics to ensure a specific execution order of operation objects. Changes in the readiness of an operation can change the resulting execution order. If you need operations to execute in a specific order, use operation-level dependencies as defined by the NSOperation class.
To establish explicit dependencies, you might do something like:
let oldify = NSBlockOperation() {
NSLog("oldify")
}
oldify.completionBlock = {
NSLog("oldify completion")
}
let appendify = NSBlockOperation() {
NSLog("appendify")
}
appendify.completionBlock = {
NSLog("appendify completion")
}
appendify.addDependency(oldify)
let nettoyify = NSBlockOperation() {
NSLog("nettoyify")
}
nettoyify.completionBlock = {
NSLog("nettoyify completion")
}
nettoyify.addDependency(appendify)
let queue = NSOperationQueue()
queue.addOperations([oldify, appendify, nettoyify], waitUntilFinished: false)
BTW, as you'll see above, you should not add operations to the main queue in conjunction with the waitUntilFinished. Feel free to add them to a different queue, but don't dispatch from a serial queue, back to itself, with the waitUntilFinished option.

Related

How to use gcd barrier in iOS?

I want to use gcd barrier implement a safe store object. But it not work correctly. The setter sometime is more early than the getter. What's wrong with it?
https://gist.github.com/Terriermon/02c446d1238ad6ec1edb08b607b1bf05
class MutiReadSingleWriteObject<T> {
let queue = DispatchQueue(label: "com.readwrite.concurrency", attributes: .concurrent)
var _object:T?
var object: T? {
#available(*, unavailable)
get {
fatalError("You cannot read from this object.")
}
set {
queue.async(flags: .barrier) {
self._object = newValue
}
}
}
func getObject(_ closure: #escaping (T?) -> Void) {
queue.async {
closure(self._object)
}
}
}
func testMutiReadSingleWriteObject() {
let store = MutiReadSingleWriteObject<Int>()
let queue = DispatchQueue(label: "com.come.concurrency", attributes: .concurrent)
for i in 0...100 {
queue.async {
store.getObject { obj in
print("\(i) -- \(String(describing: obj))")
}
}
}
print("pre --- ")
store.object = 1
print("after ---")
store.getObject { obj in
print("finish result -- \(String(describing: obj))")
}
}
Whenever you create a DispatchQueue, whether serial or concurrent, it spawns its own thread that it uses to schedule and run work items on. This means that whenever you instantiate a MutiReadSingleWriteObject<T> object, its queue will have a dedicated thread for synchronizing your setter and getObject method.
However: this also means that in your testMutiReadSingleWriteObject method, the queue that you use to execute the 100 getObject calls in a loop has its own thread too. This means that the method has 3 separate threads to coordinate between:
The thread that testMutiReadSingleWriteObject is called on (likely the main thread),
The thread that store.queue maintains, and
The thread that queue maintains
These threads run their work in parallel, and this means that an async dispatch call like
queue.async {
store.getObject { ... }
}
will enqueue a work item to run on queue's thread at some point, and keep executing code on the current thread.
This means that by the time you get to running store.object = 1, you are guaranteed to have scheduled 100 work items on queue, but crucially, how and when those work items actually start executing are up to the queue, the CPU scheduler, and other environmental factors. While somewhat rare, this does mean that there's a chance that none of those tasks have gotten to run before the assignment of store.object = 1, which means that by the time they do happen, they'll see a value of 1 stored in the object.
In terms of ordering, you might see a combination of:
100 getObject calls, then store.object = 1
N getObject calls, then store.object = 1, then (100 - N) getObject calls
store.object = 1, then 100 getObject calls
Case (2) can actually prove the behavior you're looking to confirm: all of the calls before store.object = 1 should return nil, and all of the ones after should return 1. If you have a getObject call after the setter that returns nil, you'd know you have a problem. But, this is pretty much impossible to control the timing of.
In terms of how to address the timing issue here: for this method to be meaningful, you'll need to drop one thread to properly coordinate all of your calls to store, so that all accesses to it are on the same thread.
This can be done by either:
Dropping queue, and just accessing store on the thread that the method was called on. This does mean that you cannot call store.getObject asynchronously
Make all calls through queue, whether sync or async. This gives you the opportunity to better control exactly how the store methods are called
Either way, both of these approaches can have different semantics, so it's up to you to decide what you want this method to be testing. Do you want to be guaranteed that all 100 calls will go through before store.object = 1 is reached? If so, you can get rid of queue entirely, because you don't actually want those getters to be called asynchronously. Or, do you want to try to cause the getters and the setter to overlap in some way? Then stick with queue, but it'll be more difficult to ensure you get meaningful results, because you aren't guaranteed to have stable ordering with the concurrent calls.

Synchronise async tasks in a serial queue

let serialQueue = DispatchQueue(label: "Serial Queue")
func performCriticalSectionTask() {
serialQueue.async {
performLongRuningAsyncTask()
}
}
func performLongRuningAsyncTask() {
/// some long running task
}
The function performCriticalSectionTask() can be called from different places many times.
I want this function to be running one at a time. Thus, I kept the critical section of code inside the serial async queue.
But, the problem here is that the critical section itself is a performLongRuningAsyncTask() which will return immediately, and thus serial queue will not wait for the current task to complete first and will start another one.
How can I solve this problem?
if performLongRuningAsyncTask is only running in one thread, it will be called only once at the time. In your case it delegates it to another thread, so you wrapping it into another thread call doesn't work since it will be on another thread anyway
You could do checks in the method itself, the simplest way is to add a boolean. (Or you could add these checks in your class that executes this method, with a completion handler).
Another ways are adding dispatch groups / semaphores / locks.
If you still need it to be executed later, you should use a dispatch group / OperationQueue / Semaphore.
func performLongRunningAsyncTask() {
self.serialQueue.sync {
if isAlreadyRunning {
return
}
isAlreadyRunning = true
}
asyncTask { result in
self.serialQueue.sync {
self.isAlreadyRunning = false
}
}
}

NSAsynchronousFetchRequest - should update be explicitly done on main thread

I am creating an NSAsynchronousFetchRequest which has a completion block inside it.
I have seen various examples where some include using dispatch queue on the main thread and others don't. For example the Ray Wenderlich core data book doesn't call the result on the main thread.
Should I go back on the main thread when executing the result. Initially I thought I had to but now I don't. Some definitive clarity would be great.
fun exampleFetch(_ completionHandler: #escaping () -> () {
let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "Example")
let asyncFetchRequest = NSAsynchronousFetchRequest<NSDictionary>(fetchRequest: fetchRequest) { result in
// DispatchQueue.main.async { // is this needed
completion()
//}
}
managedContext.performChanges {
do {
try self.managedContext.execute(asyncFetchRequest)
} catch let error {
print("error trying to fetch saving objects:", error.localizedDescription)
}
}
}
You should not explicitly call the completion handler on the main queue. Let the caller decide how to handle it. If anything, document that the completion handler will be called on an arbitrary queue. Then the client calling your exampleFetch method knows that it is their responsibility to be sure that process the result on whatever queue it needs.
This gives the client more control.
This also prevents a lot of needless thread switching. A client may call exampleFetch from a background queue and it may want to process the results in the background. If you explicitly put the completion on the main queue, the client then needs to explicitly switch back to a background queue to process the result. That's two needless queue switches and it's wasted effort on the main queue.

What's the difference between operation and block in NSOperation?

When I use following codes:
let queue = OperationQueue()
let operation = BlockOperation()
for i in 0..<10 {
operation.addExecutionBlock({
print("===\(Thread.current)===\(i)"
})
}
queue.addOperation(operation)
I create a asynchronous queue to execute these operations.
And if I use codes like following:
let queue = OperationQueue()
for i in 0..<10 {
queue.addOperation(
print("===\(Thread.current)===\(i)"
)
}
When I make the queue concurrent,they produce the same result.
But when I set
queue.maxConcurrentOperationCount = 1
to make the queue serial, they are different!
The first one still print the unordered result like the concurrent queue. But the second one can print the ordered result.
So what's the difference between them? When I want to use NSOperation, which one should I use? Any help much appreciated!
The docs on OperationQueue tell you about concurrency and order of execution of the blocks you submit. You should read the Xcode article on OperationQueue. Here is a relevant bit:
An operation queue executes its queued operation objects based on
their priority and readiness. If all of the queued operation objects
have the same priority and are ready to execute when they are put in
the queue—that is, their isReady method returns true—they are executed
in the order in which they were submitted to the queue. However, you
should never rely on queue semantics to ensure a specific execution
order of operation objects. Changes in the readiness of an operation
can change the resulting execution order. If you need operations to
execute in a specific order, use operation-level dependencies as
defined by the Operation class.
Check please the official documentation regarded addExecutionBlock: function. It just adds the specified block to the receiver's list of blocks to perform in context of executing operation.
If you would like to do it synchronously, here is a code sample:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
for i in 0..<10 {
let operation = BlockOperation {
print("===\(Thread.current)===\(i)")
}
queue.addOperation(operation)
}
When I want to use NSOperation, which one should I use?
Use the second one.
Just a guess.
In this case:
let queue = OperationQueue()
let operation = BlockOperation()
for i in 0..<10 {
operation.addExecutionBlock({
print("===\(Thread.current)===\(i)"
})
}
queue.addOperation(operation)
Inside the BlockOperation, blocks are asynchronous while the BlockOperation itself
is synchronous. So it actually is a synchronous queue.
So the use of queue.addOperation(operation) is nonsense. Instead of it,
I should use operation.start() because this is a synchronous queue.
The function addExecutionBlock() should be used when you need a synchronous queue.
The function addOperation() should be used when you need a asynchronous queue.
Difference -> BlockOperation has a addDependency whereas OperationQueue() needs to addOperations. Following code with console output will elaborate:
let opQueue = OperationQueue()
opQueue.addOperation {
print("operation 1")
}
let operation2 = BlockOperation {
print("operation 2")
}
let operation3 = BlockOperation {
print("operation 3")
}
operation2.addDependency(operation3)
opQueue.addOperation(operation2)
opQueue.addOperation(operation3)
Console output:
operation 1
operation 3
operation 2

Swift 3 multithreading using which queue?

I'm going through Stanford CP 193P, looking at a Twitter client.
When a network is called, I assumed it would always be called on the main queue unless invoked on another queue. However without dispatch back onto the main queue (as below) the App does not work as expected - meaning we must not be on the main queue. How?
When tweets are fetched the following closure is used - and to update the UI means that the work needs to be done on the main thread (DispatchQueue.main.async)
request.fetchTweets { [weak self] (newTweets) in
DispatchQueue.main.async {
if request == self?.lastTwitterRequest {
self?.tweets.insert(newTweets, at: 0)
self?.tableView.insertSections([0], with: .fade)
}
}
}
This calls a convenience function that is commented as "handler is not necessarily invoked on the main queue". I can't find anywhere that declares which queue it is invoked on, so I assume it is on the main queue?
// convenience "fetch" for when self is a request that returns Tweet(s)
// handler is not necessarily invoked on the main queue
open func fetchTweets(_ handler: #escaping ([Tweet]) -> Void) {
fetch { results in
var tweets = [Tweet]()
var tweetArray: NSArray?
if let dictionary = results as? NSDictionary {
if let tweets = dictionary[TwitterKey.Tweets] as? NSArray {
tweetArray = tweets
} else if let tweet = Tweet(data: dictionary) {
tweets = [tweet]
}
} else if let array = results as? NSArray {
tweetArray = array
}
if tweetArray != nil {
for tweetData in tweetArray! {
if let tweet = Tweet(data: tweetData as? NSDictionary) {
tweets.append(tweet)
}
}
}
handler(tweets)
}
}
I did not write the Twitter framework, and it appears to have been authored by the Stanford instructor.
You ask:
This calls a convenience function that is commented as "handler is not necessarily invoked on the main queue". I can't find anywhere that declares which queue it is invoked on, so I assume it is on the main queue?
No, you cannot assume it is on the main queue. In fact, it sounds like it's explicitly warning you that it isn't. The only time you can be assured it's on the main queue, is if it explicitly says so.
For example, if the underlying framework is using URLSession, it, by default, does not use the main queue for its completion handlers. The init(configuration:​delegate:​delegate​Queue:​) documentation warns us that the queue parameter is as follows:
An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
And for a given framework, it may be completely unrelated to URLSession queue behavior. It might also be using its own queues for completion handlers.
Bottom line, if the framework doesn't explicitly assure you that the closure always runs on the main queue, you should never assume it does. So, yes, in the absence of any assurances to this effect, you'd want to dispatch any UI stuff to the main queue and do the appropriate synchronization for any model objects.
You can, if you have code that must run on a particular thread and you want to make sure this is the case, you can add a dispatchPrecondition to test if it's on the main thread. The behavior of this changes between debug builds and release builds, but it's a quick way of quickly testing if it's using the queue you think it is:
dispatchPrecondition(condition: .onQueue(.main))

Resources