dispatch_after not always working - objective-c

I tried our the following simple test to understand the QoS questions in Interaction between qualityOfService property of NSOperationQueue & NSOperation added to it
While doing this, I am running into a weird issue, where the code inside a dispatch_after is not always working. Can someone help me understand why the case 2 is not working.
In this case, the cancel code inside dispatch_after gets executed
NSBlockOperation *newOp = [NSBlockOperation new];
__weak NSBlockOperation *weakOp = newOp;
[newOp addExecutionBlock:^{
NSBlockOperation *innerOp = weakOp;
while (![innerOp isCancelled])
{
usleep(2000000) ;
NSLog(#"New Op QOS is %ld",innerOp.qualityOfService);
}
NSLog(#"Exiting snce new Op is cancelled");
}];
[self.myCustomQ addOperation:newOp];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSBlockOperation *innerOp = weakOp;
[innerOp cancel];
NSLog(#"Cancelling block 2");
});
But in this case, it is not getting executed
self.myMainQ = [NSOperationQueue mainQueue];
NSLog(#"QOS of main Q is %ld",self.myMainQ.qualityOfService);
__weak ViewController *weakSelf = self;
self.fromMainOp = [NSBlockOperation blockOperationWithBlock:^{
ViewController *innerSelf = weakSelf;
while (![innerSelf.fromMainOp isCancelled])
{
usleep(1000000) ;
NSLog(#"Main OP QOS is %ld",innerSelf.fromMainOp.qualityOfService);
}
}];
NSLog(#"QOS of main op is %ld",self.fromMainOp.qualityOfService);
[self.myMainQ addOperation:self.fromMainOp];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
ViewController *innerSelf = weakSelf;
[innerSelf.fromMainOp cancel];
NSLog(#"Cancelling operation");
});

self.fromMainOp = [NSBlockOperation blockOperationWithBlock:^{
ViewController *innerSelf = weakSelf;
while (![innerSelf.fromMainOp isCancelled])
{
usleep(1000000) ;
NSLog(#"Main OP QOS is %ld",innerSelf.fromMainOp.qualityOfService);
}
}];
That code looks very much like it is blocking the main queue until that block exits. If the main queue is blocked, then no blocks scheduled on the main queue are going to be executed.

Related

Block code in a method does not get run

I have the code below to load a group of images and notify me via completionHandler when they are all done loading. However, I find that certain dispatch_group_leave won't be called at times and my guess is imageLoader is deallocated before the block gets to run. If I put a reference of imageLoader within the loadImageWithURL:completionHandler block, then everything works as intended.
Is my guess of the cause correct? What's the correct fix for this issue? I know ARC does block copy in most cases automatically, should I do a block copy in this case?
- (void)loadGroupImagesAsyncWithCompletion:(void(^)(NSError *))completionHandler {
dispatch_group_t group = dispatch_group_create();
int index = 0;
for (Item *item in items) {
char queueLabel[30] = {0};
sprintf(queueLabel, "loader%d", index);
dispatch_queue_t queue = dispatch_queue_create(queueLabel, NULL);
dispatch_group_enter(group);
dispatch_async(queue, ^{
ImageLoader *imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageWithURL:url completionHandler:^(UIImage *image, NSError *error) {
if (image) {
item.image = image;
}
//NOTE: if item object is referenced in this block,
//then there is no missed dispatch_group_leave call.
dispatch_group_leave(group);
}];
});
}
// Non-blocking wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// shouldn't take more than 5 secs to load all images
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil);
});
});
}
Here's my guess. It's only a guess because you haven't posted the code of anything to do with ImageLoader.
If -loadImageWithURL:completionHandler: operates asynchronously i.e. it uses an async dispatch queue itself then you could be right that it is being deallocated before the load finishes. This is because its lifetime is just the scope of the for block it is declared in.
In fact, there is no reason why that method needs to do stuff asynchronously, because you have already got it in an asynchronous block. Just have a synchronous method and call dispatch_group_leave() after the method finishes.
EDIT
Given that you have no control over ImageLoader and -loadImageWithURL:completionHandler: operates asynchronously without any help from you, you should remove the dispatch_async wrapper around the call. You'll still have the problem of the ImageLoader being deallocated, but that can be avoided by putting each ImageLoader in a array when you create it.
The code would look something like this:
- (void)loadGroupImagesAsyncWithCompletion:(void(^)(NSError *))completionHandler
{
NSMutableArray* loaders = [[NSMutableArray alloc] init];
dispatch_group_t group = dispatch_group_create();
int index = 0;
for (Item *item in items) {
char queueLabel[30] = {0};
sprintf(queueLabel, "loader%d", index);
dispatch_queue_t queue = dispatch_queue_create(queueLabel, NULL);
dispatch_group_enter(group);
ImageLoader *imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageWithURL:url completionHandler:^(UIImage *image, NSError *error) {
if (image) {
item.image = image;
}
//NOTE: if item object is referenced in this block,
//then there is no missed dispatch_group_leave call.
dispatch_group_leave(group);
}];
[loaders addObject: imageLoader];
}
// Non-blocking wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// shouldn't take more than 5 secs to load all images
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil);
});
[loaders removeAllObjects];
});
}

NSOperationQueue inside NSOperation cause app freeze with waitUntilFinished:YES

I have NSOperation with AFHTTPClient request. In end of operation i need to perform another N operations with requests and wait that requests will be finished to mark main operation as finished
#interface MyOperation : OBOperation
#end
#implementation MyOperation
- (id)init
{
if (self = [super init]) {
self.state = OBOperationReadyState;
}
return self;
}
- (void)start
{
self.state = OBOperationExecutingState;
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://google.com"]];
[client getPath:#"/"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
NSMutableArray *ops = [NSMutableArray array];
for (int i = 1; i < 10; i++) {
MyInnerOperation *innerOp = [[MyInnerOperation alloc] initWithNumber:#(i)];
[ops addObject:innerOp];
}
[queue addOperations:ops waitUntilFinished:YES];
self.state = OBOperationFinishedState;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
self.state = OBOperationFinishedState;
NSLog(#"error");
}];
}
#end
Link to OBOperation source at end of question. It's a simple class that add useful methods to control NSOperation flow
Sample of Inner Operation:
#interface MyInnerOperation : OBOperation
- (id)initWithNumber:(NSNumber *)number;
#end
#implementation MyInnerOperation
- (id)initWithNumber:(NSNumber *)number
{
if (self = [super init]) {
_number = number;
self.state = OBOperationReadyState;
}
return self;
}
- (void)start
{
self.state = OBOperationExecutingState;
NSLog(#"begin inner operation: %#", _number);
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://google.com"]];
[client getPath:#"/"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"inner operation success: %#", _number);
self.state = OBOperationFinishedState;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
self.state = OBOperationFinishedState;
NSLog(#"inner operation error: %#", _number);
}];
}
#end
So if i begin my operation:
MyOperation *op = [MyOperation new];
[_queue addOperation:op];
I see in console begin inner operation: 1 and that's all! My app totally freeze (even UI)
After some exploration i decide that freeze caused by [queue addOperations:ops waitUntilFinished:YES];. If i don't wait for finish, my inner operations work as expected, but MyOperation finished before child operations will be completed.
So now i have workaround with dependent block operation:
NSBlockOperation *endOperation = [NSBlockOperation blockOperationWithBlock:^{
self.state = OBOperationFinishedState;
}];
NSMutableArray *ops = [NSMutableArray arrayWithObject:endOperation];
for (int i = 1; i < 10; i++) {
MyInnerOperation *innerOp = [[MyInnerOperation alloc] initWithNumber:#(i)];
[ops addObject:innerOp];
[endOperation addDependency:innerOp];
}
[queue addOperations:ops waitUntilFinished:NO];
But i still totally don't understand what's real problem of this freeze. Any explanation will be very useful.
OBOperaton class source: https://dl.dropboxusercontent.com/u/1999619/issue/OBOperation.h https://dl.dropboxusercontent.com/u/1999619/issue/OBOperation.m
Whole project: https://dl.dropboxusercontent.com/u/1999619/issue/OperationsTest.zip
The reason you're deadlocking is that AFNetworking dispatches the completion blocks to the main queue. Therefore, waitUntilFinished in that first success handler will block the main queue until the subordinate requests finish. But those subordinate requests cannot finish because they need to dispatch their completion blocks to the main queue, which the first operation is still blocking.
Clearly, you never want to block the main queue anyway, but you receive a deadlock if you block the main queue waiting for operations, which, themselves, need the main queue.

MKNetworkKit and GCD dispatch_group_t

I am trying to use MKNetworkKit to fetch an array of links from a web service, then parse each response on a background thread, and use the dispatch_group_t of GCD to wait until all threads are finished processing. Where I'm stuck is I can't figure out why my dispatch_group_notify is not waiting for all threads in the group to complete. Running this code will print:
results count: 0
added into results, count: 1
added into results, count: 2
The dispatch group is not waiting on its threads. I have also tried dispatch_group_wait but that gave me a crash. I don't know if MKNetworkKit's use of NSOperation is conflicting with this issue. Thanks for any help!
- (MKNetworkOperation *)getABunchOfMovies:(NSArray *)movies onCompletion:(CastResponseBlock)completionBlock onError:(MKNKErrorBlock)errorBlock
{
MKNetworkOperation *operation;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
block NSMutableArray *results = [[NSMutableArray alloc] initWithCapacity:[movies count]];
for (NSString *movieTitle in movies) {
operation = [self operationWithPath:REQUEST_URL(API_KEY, [movieTitle urlEncodedString])];
[operation onCompletion:^(MKNetworkOperation *completedOperation) {
dispatch_group_async(group, queue, ^{
NSDictionary *response = [completedOperation responseJSON];
id result = [self processResponse:response withMovieTitle:movieTitle];
#synchronized (results) {
[results addObject:result];
NSLog(#"added into results, count: %d", [results count]);
}
});
}
onError:^(NSError *error) {
errorBlock(error);
}];
[self enqueueOperation:operation];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"results count: %d", [results count]);
// Return array here
completionBlock(results);
});
dispatch_release(group);
return operation;
}
Edit:
I still can't figure out why, but if I change it to use dispatch_group_enter(group); and match it with a dispatch_group_leave(group); at the end of the completion block, it works. Does anyone have any idea why this is happening?
At the moment MKNetworkKit doesn't support queue completion handlers.
You should consider adding operation dependency instead of this hack.
[lastOperation addDependency:op1];
[lastOperation addDependency:op2];
and assume that when "lastOperation" completes, the queue has indeed completed.
Another way is to KVO the "operationCount" keypath on the engine and check if it reaches zero.
MKNetworkEngine has a code block that does this to show and hide the network activity indicator.

Error trying to assigning __block ALAsset from inside assetForURL:resultBlock:

I am trying to create a method that will return me a ALAsset for a given asset url. (I need upload the asset later and want to do it outside the result block with the result.)
+ (ALAsset*) assetForPhoto:(Photo*)photo
{
ALAssetsLibrary* library = [[[ALAssetsLibrary alloc] init] autorelease];
__block ALAsset* assetToReturn = nil;
NSURL* url = [NSURL URLWithString:photo.assetUrl];
NSLog(#"assetForPhoto: %#[", url);
[library assetForURL:url resultBlock:^(ALAsset *asset)
{
NSLog(#"asset: %#", asset);
assetToReturn = asset;
NSLog(#"asset: %# %d", assetToReturn, [assetToReturn retainCount]);
} failureBlock:^(NSError *error)
{
assetToReturn = nil;
}];
NSLog(#"assetForPhoto: %#]", url);
NSLog(#"assetToReturn: %#", assetToReturn); // Invalid access exception coming here.
return assetToReturn;
}
The problem is assetToReturn gives an EXC_BAD_ACCESS.
Is there some problem if I try to assign pointers from inside the block? I saw some examples of blocks but they are always with simple types like integers etc.
A few things:
You must keep the ALAssetsLibrary instance around that created the ALAsset for as long as you use the asset.
You must register an observer for the ALAssetsLibraryChangedNotification, when that is received any ALAssets you have and any other AssetsLibrary objects will need to be refetched as they will no longer be valid. This can happen at any time.
You shouldn't expect the -assetForURL:resultBlock:failureBlock:, or any of the AssetsLibrary methods with a failureBlock: to be synchronous. They may need to prompt the user for access to the library and will not always have their blocks executed immediately. It's better to put actions that need to happen on success in the success block itself.
Only if you absolutely must make this method synchronous in your app (which I'd advise you to not do), you'll need to wait on a semaphore after calling assetForURL:resultBlock:failureBlock: and optionally spin the runloop if you end up blocking the main thread.
The following implementation should satisfy as a synchronous call under all situations, but really, you should try very hard to make your code asynchronous instead.
- (ALAsset *)assetForURL:(NSURL *)url {
__block ALAsset *result = nil;
__block NSError *assetError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self assetsLibrary] assetForURL:url resultBlock:^(ALAsset *asset) {
result = [asset retain];
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
assetError = [error retain];
dispatch_semaphore_signal(sema);
}];
if ([NSThread isMainThread]) {
while (!result && !assetError) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
else {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);
[assetError release];
return [result autorelease];
}
You should retain and autorelease the asset:
// ...
assetToReturn = [asset retain];
// ...
return [assetToReturn autorelease];

Asynchronous request to the server from background thread

I've got the problem when I tried to do asynchronous requests to server from background thread. I've never got results of those requests. Simple example which shows the problem:
#protocol AsyncImgRequestDelegate
-(void) imageDownloadDidFinish:(UIImage*) img;
#end
#interface AsyncImgRequest : NSObject
{
NSMutableData* receivedData;
id<AsyncImgRequestDelegate> delegate;
}
#property (nonatomic,retain) id<AsyncImgRequestDelegate> delegate;
-(void) downloadImage:(NSString*) url ;
#end
#implementation AsyncImgRequest
-(void) downloadImage:(NSString*) url
{
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:20.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
receivedData=[[NSMutableData data] retain];
} else {
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[delegate imageDownloadDidFinish:[UIImage imageWithData:receivedData]];
[connection release];
[receivedData release];
}
#end
Then I call this from main thread
asyncImgRequest = [[AsyncImgRequest alloc] init];
asyncImgRequest.delegate = self;
[self performSelectorInBackground:#selector(downloadImage) withObject:nil];
method downloadImage is listed below:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[asyncImgRequest downloadImage:#"http://photography.nationalgeographic.com/staticfiles/NGS/Shared/StaticFiles/Photography/Images/POD/l/leopard-namibia-sw.jpg"];
[pool release];
}
The problem is that method imageDownloadDidFinish is never called. Moreover none of methods
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
are called. However if I replace
[self performSelectorInBackground:#selector(downloadImage) withObject:nil];
by
[self performSelector:#selector(downloadImage) withObject:nil];
everything is working correct. I assume that the background thread dies before async request is finished it job and this causes the problem but I'm not sure. Am I right with this assumptions? Is there any way to avoid this problem?
I know I can use sync request to avoid this problem but it's just simple example, real situation is more complex.
Thanks in advance.
Yes, the thread is exiting. You can see this by adding:
-(void)threadDone:(NSNotification*)arg
{
NSLog(#"Thread exiting");
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(threadDone:)
name:NSThreadWillExitNotification
object:nil];
You can keep the thread from exiting with:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[self downloadImage:urlString];
CFRunLoopRun(); // Avoid thread exiting
[pool release];
}
However, this means the thread will never exit. So you need to stop it when you're done.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
Learn more about Run Loops in the Threading Guide and RunLoop Reference.
You can start the connection on a background thread but you have to ensure the delegate methods are called on main thread. This cannot be done with
[[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self];
since it starts immediately.
Do this to configure the delegate queue and it works even on secondary threads:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
NSURLRequests are completely asynchronous anyway. If you need to make an NSURLRequest from a thread other than the main thread, I think the best way to do this is just make the NSURLRequest from the main thread.
// Code running on _not the main thread_:
[self performSelectorOnMainThread:#selector( SomeSelectorThatMakesNSURLRequest )
withObject:nil
waitUntilDone:FALSE] ; // DON'T block this thread until the selector completes.
All this does is shoot off the HTTP request from the main thread (so that it actually works and doesn't mysteriously disappear). The HTTP response will come back into the callbacks as usual.
If you want to do this with GCD, you can just go
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{ //
// Perform your HTTP request (this runs on the main thread)
} ) ;
The MAIN_QUEUE runs on the main thread.
So the first line of my HTTP get function looks like:
void Server::get( string queryString, function<void (char*resp, int len) > onSuccess,
function<void (char*resp, int len) > onFail )
{
if( ![NSThread isMainThread] )
{
warning( "You are issuing an HTTP request on NOT the main thread. "
"This is a problem because if your thread exits too early, "
"I will be terminated and my delegates won't run" ) ;
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{
// Perform your HTTP request (this runs on the main thread)
get( queryString, onSuccess, onFail ) ; // re-issue the same HTTP request,
// but on the main thread.
} ) ;
return ;
}
// proceed with HTTP request normally
}