dispatch_async(dispatch_get_main_queue() inside NSManagedObjectContext performBlock - ios

What I want to do is create a async core data task on a background thread so as not to chew up the main thread, but I also want to do main thread work once the work is done...
Here's my task
-(void)doTaskwithCompletion:(coreDataCompletion)complete
{
[self.backgroundManagedObjectContext performBlock:^{
// do my BG core data task
[self saveContext:self.backgroundManagedObjectContext];
complete(YES);
}];
}
Here's my block method
[[MYCoreDataManager sharedInstance]doTaskwithCompletion:^(BOOL complete) {
if (complete == YES) {
dispatch_async(dispatch_get_main_queue(), ^{
// back to the main thread
});
}
}];
Something tells me this is wrong... but I can't find another way to put myself back on the main thread once the block has completed... notifications seem way too clunky.
I suppose in a nutshell my question is can I call dispatch_async(dispatch_get_main_queue() inside moc performBlock:^?
Essentially
-(void)doTaskwithCompletion:(coreDataCompletion)complete
{
[self.backgroundManagedObjectContext performBlock:^{
// do my BG core data task
[self saveContext:self.backgroundManagedObjectContext];
dispatch_async(dispatch_get_main_queue(), ^{
// back to the main thread
});
}];
}

I guess you know that it is a very common pattern to call something async and inside go back to mainQueue, i.e. for updating the UI:
dispatch_async(globalQueue, ^{
// do something
dispatch_async(mainQueue, ^{
// update UI
});
});
As you already named your variable self.backgroundManagedObjectContext you have probably heart of Multi-Context CoreData and I understand your concerns. As long as you are not trying to change something with this block for CoreData (on any Context) you are probably fine.
Just make sure that you use the correct initializer for your contexts, i.e. [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

"I suppose in a nutshell my question is can I call dispatch_async(dispatch_get_main_queue() inside moc performBlock:^?"
The answer is yes! In fact, you are doing the REVERSE of that when you call your performBlock from the main thread, right? This is a common feature of iOS. A cleaner way to do it perhaps is passing in a completion, and in that completion calling main...
right now, you have:
dispatch_async(dispatch_get_main_queue(), ^{
// back to the main thread
completion()
});
You could also write:
-(void)doTaskwithCompletion:(coreDataCompletion)complete
{
[self.backgroundManagedObjectContext performBlock:^{
// do my BG core data task
[self saveContext:self.backgroundManagedObjectContext];
complete()
}];
}
where complete has in it a call to the main thread.

Related

Switch back to the main thread in ReactiveCocoa

I'm new to ReactiveCocoa, and therefore could miss something obvious.
I have 2 operations scheduled on the background thread, and after they complete I want to update the UI and for this I need to switch back to the main thread, but I have no idea how.
Here is snippet of code.
-(RACSignal *)executeSigninSignal {
return [[[self.services getAuthenticationService]
authenticationSignalFor:self.username andPassword:self.password]
//Return user if exists
flattenMap:^RACStream *(STUser *user) {
return [[[[self services] getContactsLoadService]
contactsLoadSignalForUser:user]
//Return user contacts
doNext:^(NSArray *contacts) {
STContactsListViewModel *contactsViewModel =
[[STContactsListViewModel alloc] initWithContactsLoadResults:contacts services:self.services];
[self.services pushViewModel:contactsViewModel];
}];
}];
}
authenticationSignalFor: and contactsLoadSignalForUser: are RACSignals that are delivered on the background thread and I want to execute contents of doNext block on the main thread.
How can I declare that this block should be executed on the main thread?
Use the deliverOnMainThread method before the doNext: method.

Bizarre Objective-C threading

I've inherited a codebase for an iOS project and I wonder what the point of this code is:
-(void) someMethod {
FMDatabaseQueue *dbQueue = self.db;
// unimportant stuff
[Async series:#[
^(successBlock success, failureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
[dbQueue inDatabase:^(FMDatabase *db) {
// do database stuff
}];
});
},
^(successBlock success, failureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
[dbQueue inDatabase:^(FMDatabase *db) {
// do other database stuff
}];
});
}
];
}
Is there any reason to code something like this?
My reading is that the Async and the dispatch_async() back to the main thread makes the threading pointless.
The code is strange, but not because of this. The method +series probably does something in background (network, computation, $whatever) and then calls the series blocks. The execution of that blocks doesn't seem to be guaranteed on the main thread. But +series cannot know, whether they have to be executed on the main thread. (Maybe there is additional computation that should be done in background. Maybe the blocks are executed parallel.)
So, if there is something done, which has to be done on the main thread, you need another dispatch_async().
But yes: You get such constellations, when people try to do everything using blocks without thinking about the need. I do not know, why that happens, but using blocks many coders try to over-engineer their code.

Block current thread till part of code ran on main thread in iOS

I have a use case where i am writing data to local couchebase database in ios. Here it will not support concurrent access of write operation. So i want to run the CRUD operation on main thread and return result after running some algorithm on data on secondary threads. when main thread took over control and executes code, current running thread is not waiting till main thread completes its operation. How can i handover result from main thread to other thread.
Ex :
+(BOOL)createDocument:(NSDictionary*)data withId:(NSString*)docId {
__block CBLDocument* doc = nil;
// NSLog(#"%d count ", [[self database] documentCount]);
dispatch_async(dispatch_get_main_queue(), ^{
if(docId.length > 0) {
doc = [[self getDatabase] documentWithID:docId];
} else {
doc = [[self getDatabase] createDocument];
}
});
//I want current thread to wait till main thread completes its execution
if(doc){
return YES;
}else{
return NO;
}
}
If you know for a fact that this method is not called from the main queue, you can use dispatch_sync:
+(BOOL)createDocument:(NSDictionary*)data withId:(NSString*)docId {
__block CBLDocument* doc = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
if(docId.length > 0) {
doc = [[self getDatabase] documentWithID:docId];
} else {
doc = [[self getDatabase] createDocument];
}
});
//I want current thread to wait till main thread completes its execution
if(doc){
return YES;
}else{
return NO;
}
}
A more generalized approach would be to create a dedicated, custom dispatch queue for your database interaction. Then, any thread (either the main thread or any background thread) that wants to interact with the database would perform a dispatch_sync to that dedicated queue.
This provides a cleaner implementation, making the functional intent more explicit, and ensures that database interaction initiated from a background thread will not block the main thread (unless, of course, the main thread happens to be initiating database interactions with this database queue at the same time). This dedicated queue approach is in the spirit of the "One Queue per Subsystem" design pattern discussed in WWDC 2012 video, Asynchronous Design Patterns with Blocks, GCD, and XPC (it's the fifth design pattern discussed in the latter part of the video).
You can make another dispatch_async call to your "current thread" from the main thread. So you'll use another function block and put your if(doc) stuff into that. That's how chaining between threads are handled with GCD API.
So the problem with your code is, createDocument returning after dispatching to another thread. Instead, you should change createDocument to take a function block argument.
+(BOOL)createDocument:(NSDictionary*)data
withId:(NSString*)docId
onComplete:(void (^)(CBLDocument*))onComplete;
And change your dispatch_async call as follows:
dispatch_async(dispatch_get_main_queue(), ^{
if(docId.length > 0) {
doc = [[self getDatabase] documentWithID:docId];
} else {
doc = [[self getDatabase] createDocument];
}
dispatch_async(yourCurrentThread, ^{
onComplete(doc);
});
});
However if you really want to BLOCK your current thread, you should use dispatch_sync instead of dispatch_async.
dispatch_sync(dispatch_get_main_queue(), ^{
...
});
return doc != nil;
Sorry if there are any syntax errors, I haven't tested this.

performblockandwait inside dispatch_async deadlock

I am using background operations heavily and I was just curious if this would ever cause a deadlock. I have a Core Data Managed Object Context set to use a private queue that is referenced (possibly simultaneously) from a few different threads using performblockandwait. Under certain conditions a background task may be kicked off via the completion of another background task.
Something like the following is a possibility since my background tasks are started when certain conditions are met, which could occur from user input on the main thread or a background task.
- (void)task1
{
__weak MyClass *weakself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0),^{
....
....
[context performBlockAndWait:^{
BOOL condition = [weakself performDbCleanup];
if (condition)
{
[weakself task2];
}
}];
});
}
- (void)task2
{
__weak MyClass *weakself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0),^{
....
....
[context performBlockAndWait:^{
[weakself performDbCleanup2];
}];
});
}
Its hard to explain exactly why I need to do things this way, but I do need performBlockAndWait in both instances because this is a highly simplified version of what is happening. Long processing happens before and after the performBlockAndWait calls that I don't want blocking access to the DB Context, and also should not be blocking the main thread.

How can I get back into my main processing thread?

I have an app that I'm accessing a remote website with NSURLConnection to run some code and then save out some XML files. I am then accessing those XML Files and parsing through them for information. The process works fine except that my User Interface isn't getting updated properly. I want to keep the user updated through my UILabel. I'm trying to update the text by using setBottomBarToUpdating:. It works the first time when I set it to "Processing Please Wait..."; however, in the connectionDidFinishLoading: it doesn't update. I'm thinking my NSURLConnection is running on a separate thread and my attempt with the dispatch_get_main_queue to update on the main thread isn't working. How can I alter my code to resolve this? Thanks! [If I need to include more information/code just let me know!]
myFile.m
NSLog(#"Refreshing...");
dispatch_sync( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getResponse:#"http://mylocation/path/to/file.aspx"];
});
[self setBottomBarToUpdating:#"Processing Please Wait..."];
queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_CONCURRENT);
connectionDidFinishLoading:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
In my connectionDidFinishLoading: I would try something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
});
Then all that file access is happening in a background queue so the main queue is not locked up. The main queue will also complete this call to connectionDidFinishLoading much more quickly, since you're throwing all the hard work onto the default queue instead, which should leave it (and the main thread) ready to accept your enqueuing of the updates to the UI which will be done by the default queue as it processes the block you just enqueued to it.
The queue handover becomes
main thread callback to connectionDidFinishLoad:
rapid handoff to default global queue releasing main thread
eventual hand off to main queue for setBottomBarToUpdating: calls
performing main queue blocks on main thread to properly update UI
eventual completion of blocks on main queue
eventual completion of blocks on default queue
You've increased concurrency (good where you've good multi-core devices) and you've taken the burden of I/O off the main thread (never a good place for it) and instead got it focused on user interface work (the right place for it).
Ideally you woud run the NSURLConnection run loop off the main thread too, but this will might be enough for you to get going.
Which run loop are you running the NSURLConnection in? If it's the main loop, you're queueing up the setBottomBarToUpdating: calls behind the work you're already doing, hence the probable reason why you're not seeing the UI update.
You could also give performSelectorOnMainThread try like so:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Contacts..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Emails..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}

Resources