comparison GCD vs. performSelectorInBackground: dispatch_async not in background - multithreading

Grand Central Dispatch is great and reduces the amount of code but why I cannot run something on a background thread?
I have made a sample application to show what I mean (none of the commented work):
- (IBAction)performSel {
[self performSelectorInBackground:#selector(doStuff) withObject:nil];
[NSThread sleepForTimeInterval:3];
[[self.view.subviews lastObject] removeFromSuperview];
}
- (IBAction)gcd {
dispatch_async(dispatch_queue_create("myGCDqueue", NULL), ^(void) {
//dispatch_sync(dispatch_queue_create("myGCDqueue", NULL), ^(void) {
//dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
//dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
//dispatch_async(dispatch_get_main_queue(), ^(void) {
//dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self doStuff]; // only for readability, will move the code on success
});
[NSThread sleepForTimeInterval:3];
[[self.view.subviews lastObject] removeFromSuperview];
}
- (void)doStuff {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
UIView *abortingView = [[UIView alloc]initWithFrame: self.view.bounds];
abortingView.backgroundColor = [UIColor whiteColor];
abortingView.alpha = 0.7;
[self.view insertSubview:abortingView atIndex:10];
[abortingView release];
[pool drain];
}
the [NSThread sleepForTimeInterval:3]; is to simulate a default UI functionality. For example if someone is switching from one navigation view to another.
Simply copy the code in a new view based application, create two buttons and connect them.

UIKit classes should be used only from an application’s main thread. (From iOS4, drawing to a graphics context is thread-safe.) You can't use UIKit stuff in a background thread.
Thus, you can only use dispatch_async(dispatch_get_main_queue(), block) in this situation.
dispatch_async(dispatch_get_main_queue(), ^(void) {
It will invoke the block on the main thread in the runloop of the main thread.
dispatch_async(dispatch_queue_create("myGCDqueue", NULL), ^(void) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
It will invoke the block in a background thread. You can't use it because you want to use UIKit in the block. And be careful dispatch_async(dispatch_queue_create(, it might cause memory leak, you have to release the serial queue that is created by dispatch_queue_create.
dispatch_sync(dispatch_queue_create("myGCDqueue", NULL), ^(void) {
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
dispatch_sync waits until the block is done.
dispatch_sync(dispatch_get_main_queue(), ^(void) {
It causes DEADLOCK.

Related

How to kill or suspend a job on global queue running in background in GCD

I have a background task to download from a webservice which runs in background and i want to suspend it if user navigates to other screens meanwhile.
Here is how i tried to download in background:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NewsResponseBO *objNewsRspnc = [objNewsParser getNewsStartingFrom:0 toEndLimit:10];
dispatch_async(dispatch_get_main_queue(), ^{
for (int i=0; i< [objNewsRspnc.arrNewsData count]; i++) {
[arrListOfNews addObject:[objNewsRspnc.arrNewsData objectAtIndex:i]];
}
isDataLoading = NO;
isBottomLoaderAdded = NO;
[loader stopAnimating];
[loader removeFromSuperview];
[bottomViewforLoader removeFromSuperview];
tbv_ListOfNews.frame = CGRectMake(tbv_ListOfNews.frame.origin.x, tbv_ListOfNews.frame.origin.y, tbv_ListOfNews.frame.size.width, tbv_ListOfNews.frame.size.height +80);
tbv_ListOfNews.contentOffset = CGPointMake(0, tbv_ListOfNews.contentSize.height);
[tbv_ListOfNews reloadData];
});
});
and Here is how i navigate on tablecell's selection:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (bottomViewforLoader != nil) {
tbv_ListOfNews.frame = CGRectMake(0, 44, 320, 416+APP_DELEGATE.floatExtraHeight) ;
[bottomViewforLoader removeFromSuperview];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NewsDetailViewController *obj_DetailNews = [[NewsDetailViewController alloc]initWithNibName:nil bundle:[NSBundle mainBundle]];
NSLog(#"indexPath.row:%d,arrListOfNews(number of object:%d) ",indexPath.row,[arrListOfNews count]);
obj_DetailNews.obj_newsDetail = [arrListOfNews objectAtIndex:indexPath.row];
[APP_DELEGATE.navController pushViewController:obj_DetailNews animated:YES];
}
any help on how to suspend dispatch_get_global_queue ?
Thanks in Advance.
You cannot suspend/resume the global concurrent queues. Furthermore, the code you've posted appears to execute synchronously. You will not be able to cancel it without modifying it to be be "cancelable" (i.e. -getNewsStartingFrom:toEndLimit: will need to check a variable somewhere indicating cancellation and voluntarily stop itself.) I don't know if you own NewsResponseBO or not, but just off the top of my head, this sound like a good job for NSURLConnection and a concurrent NSOperation owing to NSOperationQueue/NSOperation's support for cancellation (but note that NSOperations still don't get you hard cancellation).
See my answer to this question for more detail on cancellation of pending dispatch jobs and why hard cancellation isn't possible.

Using GCD to Parse KML With Apple's KMLViewer

I'm using Apple's KMLViewer to load a KML file and display it in a MapView. There are over 50,000 lines of coordinates in the KML file which, of course, causes it to load slowly. In an attempt to speed things up, I'm trying to perform the parsing in another thread using GCD.
I have it working reasonably well as far as it is displaying properly and the speed is acceptable. However, I'm getting intermittent runtime errors when loading the map. I suspect it is because the way I have things laid out, the UI is being updated within the GCD block. Everything I'm reading says the UI should be updated in the main thread or else runtime errors can occur which are intermittent and hard to track down. Well, that's what I'm seeing.
The problem is, I can't figure out how to update the UI in the main thread. I'm still new to iOS programming so I'm just throwing things against the wall to see what works. Here is my code, which is basically Apple's KMLViewerViewController.m with some modifications:
#import "KMLViewerViewController.h"
#implementation KMLViewerViewController
- (void)viewDidLoad
{
[super viewDidLoad];
activityIndicator.hidden = TRUE;
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
// Locate the path to the route.kml file in the application's bundle
// and parse it with the KMLParser.
NSString *path = [[NSBundle mainBundle] pathForResource:#"BigMap" ofType:#"kml"];
NSURL *url = [NSURL fileURLWithPath:path];
kmlParser = [[KMLParser alloc] initWithURL:url];
[kmlParser parseKML];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
// Add all of the MKOverlay objects parsed from the KML file to the map.
NSArray *overlays = [kmlParser overlays];
[map addOverlays:overlays];
// Add all of the MKAnnotation objects parsed from the KML file to the map.
NSArray *annotations = [kmlParser points];
[map addAnnotations:annotations];
// Walk the list of overlays and annotations and create a MKMapRect that
// bounds all of them and store it into flyTo.
MKMapRect flyTo = MKMapRectNull;
for (id <MKOverlay> overlay in overlays) {
if (MKMapRectIsNull(flyTo)) {
flyTo = [overlay boundingMapRect];
} else {
flyTo = MKMapRectUnion(flyTo, [overlay boundingMapRect]);
}
}
for (id <MKAnnotation> annotation in annotations) {
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0);
if (MKMapRectIsNull(flyTo)) {
flyTo = pointRect;
} else {
flyTo = MKMapRectUnion(flyTo, pointRect);
}
}
// Position the map so that all overlays and annotations are visible on screen.
map.visibleMapRect = flyTo;
});
});
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
activityIndicator.hidden = FALSE;
[activityIndicator startAnimating];
}
#pragma mark MKMapViewDelegate
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
return [kmlParser viewForOverlay:overlay];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
return [kmlParser viewForAnnotation:annotation];
}
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView
{
[activityIndicator stopAnimating];
activityIndicator.hidden = TRUE;
}
#end
Suggestions?

How do I pass data values back out from a Grand Central Dispatch _asych block for use by main thread?

The Title is the whole question. If the _asych block of code produces meaningful work it will in some cases have produced information which the main thread would now like to use.
In this bare example, how would you get the data value, the string data, contained in myData out of the block for the main thread to work with:
dispatch_queue_t myQueue = dispatch_queue_create("com.mycompany.myqueue", 0);
dispatch_async(myQueue, ^{
NSString *myData = [self getSavedData];
});
dispatch_async(myQueue, ^{ dispatch_release(myQueue); });
Please extend the code help to show me, in simple usage, where and how this NSLog, or its correct equivalent, would be placed in the main thread of the program relative to the GCD block:
NSLog(#"%#", myData);
You can nest blocks, yet have each run in different threads.
dispatch_queue_t myQueue = dispatch_queue_create("someid", 0);
dispatch_async(myQueue, ^{
NSString *myData = [self getSavedData];
dispatch_async(dispatch_get_main_queue(), ^{
self.someLabel.text = myData;
});
});
dispatch_async(myQueue, ^{ dispatch_release(myQueue); });
If your code is long, it's unwieldy to have in nested blocks. So simply call a method inside dispatch_async like [self processData:myData].

do some work in the background and return the result

I'm trying to get the ID from a tag, using a library.
I came up with the following. the loop that's looks for a tag is done in the background and I get a correct result in tagAsString.
-(void) readTag {
NSLog(#"readTag");
unsigned char * tagUID = (unsigned char *) malloc(M1K_UID_SIZE * sizeof(char));
//work to do in the background
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ERR ret;
while ((ret = scanner->IsTagAvailable(tagUID)) != ERR_TAG_AVAILABLE) {
NSLog(#"ret: %d", ret);
}
//main thread
dispatch_async( dispatch_get_main_queue(), ^{
if(ret == ERR_TAG_AVAILABLE) {
NSLog(#"tag available");
NSString *tagAsString = [[[NSString alloc] initWithFormat:#"%x%x%x%x", tagUID[0],tagUID[1],tagUID[2],tagUID[3]] retain];
}
});
});
}
I would like to be able to return that value so I would be able to call:
NSString * myTag = [self readTag];
is that possible ?
Thanks for your help, Michael
It is possible, however the problem with returning a string from that function is that it would need to hold up your calling thread whilst you perform the work in the background - thus losing the benefit of the background thread. (dispatch_sync is what you would use to do that - however I would not recommend it).
When using blocks it is best to restructure your program to fit better with the asynchronous paradigm. When the work is complete it should notify whatever is waiting on the result by sending a message to it with the result. In your example you would put this in the block of code you dispatch on the main queue.
#interface TagManager
- (void)fetchTag;
- (void)tagFetched:(NSString *)tag;
#end
#implementation TagManager
- (void)fetchTag {
// The following method does all its work in the background
[someObj readTagWithObserver:self];
// return now and at some point someObj will call tagFetched to let us know the work is complete
}
- (void)tagFetched:(NSString *)tag {
// The tag read has finished and we can now continue
}
#end
Then your readTag function would be modified as so:
- (void)readTagWithObserver:(id)observer {
...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
...
dispatch_async(dispatch_get_main_queue(), ^{
if (tag is ok) {
[observer tagFetched:tag];
}
});
});
}
The main idea is that you need to split your processing up into two stages
requesting that some work is done (fetchTag in my example)
process the result when it finishes (tagFetched: in my example)

Multithread core data merge error

The standard way of using multithread core-data is
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
However, the one have external managed object that save immediately after another. This creates errors. For example:
- (void)mergeChanges:(NSNotification *)notification
{
NSManagedObjectContext *mainContext = [self managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
- (void) loadingIntoCoreData
{
NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] init];
[ctx setUndoManager:nil];
[ctx setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:ctx];
...
// Create many objects
[ctx save:&error];
[self doSomethingWithThisCtx:ctx];
}
- (void) doSomethingWithThisCtx:(NSManagedObjectContext *)ctx{
// Form relationships with objects create in - (void) loadingIntoCoreData
[ctx save:&error];
}
Then on the second [ctx save:&error] will throw an "EXC_BAD_ACCESS" error.
How would one insert into core data and create relationships with the new objects on a separate thread? It works fine on the main thread but since it's done during applicationDidLaunched, then the UI get locked up.
You usually don't call the main thread context directly. Instead, you register the object that holds the main thread context for the NSManagedObjectContextDidSaveNotification of the context on the background thread. Upon recent of the notification, call the main thread context's NSManagedObjectContextDidSaveNotification: and pass it the notification object. The main thread will then update itself.
This makes sure that the main thread is updated before it tries to use any of the objects added or deleted on the background thread.

Resources