I have set up an operation queue:
func initialiseOperationQueue(){
self.operationQueue = NSOperationQueue()
self.operationQueue.name = "General queue"
self.operationQueue.maxConcurrentOperationCount = 2
}
Then I added an operation to my queue
let op = HPSyncDataOperation(type: HPSyncDataOperationType.OnlineRecord, delegate: self, date: self.latestLastUpdateAt)
self.operationQueue.addOperation(op)
It is basically using Parse framework to asynchronously download some record data online. Its implementation looks like the following:
PFCloud.callFunctionInBackground("recordPosts", withParameters: param, block: { (objects:AnyObject!, error:NSError!) -> Void in
if error == nil {
let dataObjects = objects as [PFObject]
//TROUBLE HERE:
for object in dataObjects {
object.pinWithName("Received Posts")
}
//abcdefg
}
})
But in execution, when object.pinWithName("Received Posts") is run, it invokes
Warning: A long-running operation is being executed on the main thread.
Should an operation be run on a separate thread? So pinWithName, regardless of its sync or async, should be run on a separate thread as well?
Please help! Why is this?
Your operation will be run on a background thread, but all it's doing is starting another asynchronous process (PFCloud.callFunctionInBackground) which will start another thread. When that other process is complete it calls the completion block on the main thread.
So, in this case your operation and queue are doing basically nothing, and really you should be taking the result of the call to PFCloud.callFunctionInBackground (i.e. objects) and processing that on a background thread if it's likely to be time consuming.
Related
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.
I have implemented following completion block, one block is completed and then I update UI and object accordingly.
func doPaging() {
fetchProducts(page: pageNumber , completion: { success in
if let products = success as? Products
{
DispatchQueue.main.async {
self.products.append(contentsOf:products)
self.isWating = false;
self.productTableView.reloadData()
}
}
})
}
func fetchProducts(page: Int, completion: #escaping ((AnyObject) -> Void)) {
// URLSession call here
}
However, the following approach clearly shows restful call will happen in background thread and once it is completed, then update UI and objects.
func doPaging() {
DispatchQueue.global(qos: .background).async {
// Background Thread
fetchProducts()
DispatchQueue.main.async {
self.pageNumber += 1
self.productTableView.reloadData()
self.isWating = false
}
}
}
func fetchProducts(page: Int) {
// URLSession call here
}
I am confused between completion block method vs. DispatchQueue.
Which one is recommended?
In the first approach, you call a method fetchProducts() which internally uses NSURLSession. REST call using NSURLSession runs in background and on completion of the REST call, the completion of the task will be called. In that completion, you call your completion handler of fetchProducts(). This approach seems fine to me.
In the second approach, you use global background queue and asynchronously call NSURLSession APIs (I assume so), and don’t wait for the call to complete. The code on main queue will be instantly called and at this point the NSURLSession task may or may not have been completed.
So, this approach is problematic.
First method seems OK as long as you fetchProducts asynchronously. In fetchProducts() , if you call the completion block in the main queue you won't even need to get main queue again in the doPaging() method.
In your second method, you are calling fetchProducts() in a global (concurrent) queue. Although global queues start each task in the order they were added to queue, they run tasks concurrently. And since fechtProduct() takes time, your code block that contains self.pageNumber += 1 executed before even fetchProduct's URLSession is started. So, this approach won't work.
Completion block and Dispatch Queue are two different concepts.
Completion block is used when your function perform actions takes time to run, and need to return back and run some code even the functions has "ended". For example,
func networkCall(foo: Int, completion:#escaping (_ result:Bool)-> Void))
func otherFunc(){...}
func A(){
networkCall(foo:1){ (success) in
// handle your stuff
}
otherFunc()
}
When you run A(), it first run networkCall(), however networkCall() may takes time to run the network request and the app moved on to run otherFunc(). When the network request is done, networkCall() can call it's completion block so that A() can handle it again.
Dispatch Queue is the threading stuff safely encapsulated by Apple. Network request can be performed in Main thread as well, but it will be blocking other functions.
A common practice is to call Network request in background queue
DispatchQueue.global(qos: .background).async and call completion block after finished. If anything needs to be updated in main thread like UI, do it in the DispatchQueue.main.async
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
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:delegateQueue:) 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))
I have the following code:
func testFunc(completion: (Bool) -> Void) {
let queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1
for i in 1...3 {
queue.addOperationWithBlock{
Alamofire.request(.GET, "https://httpbin.org/get").responseJSON { response in
switch (response.result){
case .Failure:
print("error")
break;
case .Success:
print("i = \(i)")
}
}
}
//queue.addOperationAfterLast(operation)
}
queue.waitUntilAllOperationsAreFinished()
print("finished")
}
and output is:
finished
i = 3
i = 1
i = 2
but I expect the following:
i = 3
i = 1
i = 2
finished
So, why queue.waitUntilAllOperationsAreFinished() don't wait?
Each operation you've added into queue is immediately executed because Alamofire.request simply returns without waiting for the response data.
Furthermore, there is a possibility of deadlock there. Since responseJSON block is executed within the main queue by default, blocking the main thread by calling waitUntilAllOperationsAreFinished will prevent it from executing the completion block at all.
First, in order to fix the deadlock issue, you can tell Alamofire to execute the completion block in a different queue, second, you can use dispatch_group_t to group the number of asynchronous HTTP requests and keep the main thread waiting till all those requests in the group finish executing:
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
let group = dispatch_group_create()
for i in 1...3 {
dispatch_group_enter(group)
Alamofire.request(.GET, "https://httpbin.org/get").responseJSON(queue: queue, options: .AllowFragments) { response in
print(i)
dispatch_async(dispatch_get_main_queue()) {
// Main thread is still blocked. You can update the UI here but it will take effect after all HTTP requests are finished.
}
dispatch_group_leave(group)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
print("finished")
I would suggest you to use KVO and observe when the queue has finish all the task instead of blocking the current thread until all the operations finished. Or you can use dependencies. Take a look at this SO question
To check whether all operations finished - We could use KVO to observe number of operations in the Queue. Unfortunately both operations and operationCount are currently deprecated..!
So it's safe to use following option using dependency.
To check few operations are finished - Use Dependencies :
Create a final operation called "finishOperation" then add dependencies to all other required operation. This way, "finishOperation" will be executed only when depended operations are finished. Check this answer for code sample.