I have to run a method with block, several times inside a for loop.
I also have to wait until all the blocks execution completes.
My problem is that I can't understand what I do wrong, that causes my entire app to freeze. Here is the code:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//1 - creating semaphore
for(int i = 0; i< myObj.count; i++){
[[DataManager shared] verifyObjectId:myObj[i].id
completionBlock:^(BOOL found) {
if(found){
//code here
dispatch_semaphore_signal(semaphore);//3 - signaling semaphore to continue
}
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//2 - getting semaphore to wait
}
//I want to continue once all DB checks complete
Now, I don't understand, why the semaphore won't release, and the for loop won't continue.
What I actually need, is for the semaphore to release after all the DB checks complete. Ideally, I would want the semaphore to wait outside the for loop. Any suggestions on how to accomplish this?
EDIT: SOLUTION: (based on the accepted answer)
// create a group
dispatch_group_t group = dispatch_group_create();
for(int i = 0; i< myObj.count; i++){
// pair a dispatch_group_enter for each dispatch_group_leave
dispatch_group_enter(group);
[[DataManager shared] verifyObjectId:myObj[i].id
completionBlock:^(BOOL found) {
if(found){
//code here
}
dispatch_group_leave(group); //1 leave
}];
//Get a notification on a block that will be scheduled on the specified queue
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(#"-all done!-");
//code here
});
}
Without access to verifyObjectid:completiongBlock:, there are a couple of issues. First, you only call dispatch_semaphore_signal if found is true. If found is every false, you'll deadlock. That may just be a transcription error and your real code might not do that.
Another guess is that the completion block is being submitted to the queue that you're currently running on (the main queue?) If that's true, then that would definitely be a deadlock, because you'll never run dispatch_semaphore_signal since it's waiting on dispatch_semaphore_wait. I can't tell without information about DataManager.
Your approach also serializes the calls, whereas I think you wanted them to be in parallel. Each call has to wait for the former one to finish in your code.
The better tools to use here are dispatch_apply and dispatch_group. Something like this (untested):
dispatch_group_t group = dispatch_group_create();
dispatch_apply(myObj.count, dispatch_get_global_queue(0, 0), ^(size_t i){
dispatch_group_enter(group);
[[DataManager shared] verifyObjectId:myObj[i].id
completionBlock:^(BOOL found) {
if(found){
//code here
}
dispatch_group_leave(group));
}];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_apply won't return until all the blocks have completed running, which means that dispatch_group_enter has run "count" times. You then use dispatch_group_wait to wait for all the calls to dispatch_group_leave.
Related
I am creating a serial queue in which i add two task as shown below
dispatch_queue_t serial = dispatch_queue_create("com.apple.serial", DISPATCH_QUEUE_SERIAL);
**//Task 1**
dispatch_async(serial, ^{
[NMUserAPIManager getUserProfileData:^(NMUser *objUser) {
NSLog(#"Get User Profile .....");
_objUser = objUser;
}];
});
**//Task 2**
dispatch_async(serial, ^{
[NMUserAPIManager getUserRecentTransactionData:^(NSDictionary *responseDictionary) {
_accountTableView.hidden = NO;
[self recentTransactionSetup:responseDictionary];
NSLog(#"Get User Recent transaction");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadTableData];
});
}];
});
Inside that two task i am calling web service with NSURLSession. Problem is that before my Task 1 completion handle Task2 completion handle get called. According to theory by using serial queue each task waits for the previous task to finish before being executed. It my understanding is correct.
NSURLSession's already run on a background thread, so the issue you are seeing here is that as far as your serial queue is concerned once you call 'getUserProfileData:' technically the work for that block in your queue is finished because the NSURLSession is running on a different thread. If your main goal here is to simply call your second task after your first one completes I don't think you need your own queue you would probably be better off simply doing something like:
[NMUserAPIManager getUserProfileData:^(NMUser *objUser) {
NSLog(#"Get User Profile .....");
_objUser = objUser;
[self getUserTransactions];
}];
-(void)getUserTransactions
{
[NMUserAPIManager getUserRecentTransactionData:^(NSDictionary *responseDictionary) {
_accountTableView.hidden = NO;
[self recentTransactionSetup:responseDictionary];
NSLog(#"Get User Recent transaction");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadTableData];
});
}];
}
EDIT:
If you are looking for something a little more robust I would check out this post for how you can subclass NSOperation to make your own Asynchronous Operation which you can then use with an NSOperationQueue.
Needed some advice/review on possible downsides of using a dispatch group inside another group, if it could lead to a race condition/deadlock or just wrong practice.
1) Can a dispatch_group_enter exist inside the scope of another group? I could not find an example from Apple following such practice. Remember, secondCall needs to happen after firstCall. There is a dependency. Thoughts?
2) What would be a good design to execute a thirdCall - which again depends on result of firstCall result. But agnostic of the completionHandler timing i.e. can happen later and doesn't need to wait for completionHandler to finish.
Here's a simplified example of the completion handler incorporating 3 calls -
-(void)someMethod:(void (^)(NSError *error))completionHandler {
dispatch_group_t serviceGroup = dispatch_group_create();
dispatch_group_enter(serviceGroup);
__typeof__(self) __weak weakSelf = self;
[self.obj firstCall completion:^(NSError *firstError) {
__typeof__(self) strongSelf = weakSelf;
// Second Call
if (!firstError.code) {
dispatch_group_enter(serviceGroup);
[strongSelf.obj secondCall completion:^(void) {
dispatch_group_leave(serviceGroup);
}];
}
// Third call
if (!firstError.code) {
[strongSelf executeThirdCall];
}
dispatch_group_leave(serviceGroup);
}]; // Closing block for first call.
dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
if (completionHandler) {
completionHandler(error);
}
});
}
Some classic examples of dispatch groups can be found in this answer.
I can't think of any issues with this code.
However I am not sure you need dispatch groups at all for this example.
You are executing three requests. Request 2 and Request 3 both depend on the result of the Request 1. You need to call the function's completionHandler when Request 2 is finished. Can't you do it into the completion handler of Request 2?
So, I've been searching all over but didn't find a solution, or at least I couldn't apply it.
I've found this thread here on stackoverflow, but didn't succeed in implementing it in my code.
My issue is, that I need to know when nested AFNetworking calls and For loops are done. I've tried it with GCD groups, but with no luck.
The code looks like this:
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_async(queue, ^{
[[JSON GET method using AFNetworking 2.0] success:^(NSArray *result) {
dispatch_group_async(group, queue, ^{
//do some work with the result
for (NSDictionary *resultPartDictionary in result) {
dispatch_group_async(group, queue, ^{
//do some more work with parts of the result
[[JSON GET method based on result] success:^(NSArray *result) {
dispatch_group_async(group, queue, ^{
//do some work
for (NSDictionary *resultPartDictionary in result) {
dispatch_group_async(group, queue, ^{
//do some work
[[JSON GET method based on result] success:^(NSArray *result) {
dispatch_group_async(group, queue, ^{
//do some work
for (NSDictionary *resultPartDictionary in result) {
dispatch_group_async(group, queue, ^{
//do some work
});
}
});
}];
});
}
});
}];
});
}
});
}
});
}
Right now, everything works. I'm handling Core Data inside the blocks, so I needed MOCs for every thread, which works as well.
The only thing I'd like to know is how to know when all these blocks finish.
Thank you!
EDIT
So, I've tried using dispatch_group_enter(group) and dispatch_group_leave(group), but it seems to me, that it's just not possible with this embedded architecture. Because of the For loops, the "leave" notifications are either too many, which causes an exception or not enough and the dispatch_group_notify returns too early.
Any ideas on this?
You are looking for dispatch_group_notify and dispatch_group_enter/dispatch_group_leave.
dispatch_group_notify executes the given block in the given queue, when every block in the group is finished.
dispatch_group_enter increases the current count of executing tasks in the group. Every dispatch_group_enter must be balanced with a call to dispatch_group_leave.
dispatch_group_leave decreases the current count of executing tasks in the group.
So, you should trick dispatch_group_notify with increase the number of the tasks in the group before your network calls start and decrease it when everything finished. To achieve this, call dispatch_group_enter before dispatch_async and call dispatch_group_leave in the last thread. Since you know the element count of the every result array, you can check if the current thread is the last one.
dispatch_group_enter(group); // Increases the number of blocks in the group.
dispatch_async(queue, ^{
// Make your AFNetworking calls.
dispatch_group_async(group, queue, ^{
//do some work.
if (isLastThread)
dispatch_group_leave(group); // Decreases the number of blocks in the group.
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // Calls the given block when all blocks are finished in the group.
// All blocks finished, do whatever you like.
});
I'm struggling to figure out the best method to test interacting with Core Data in a background thread. I have the following class method:
+ (void)fetchSomeJSON
{
// Download some json then parse it in the block
[[AFHTTPClient sharedClient] fetchAllThingsWithCompletion:^(id results, NSError *error) {
if ([results count] > 0) {
NSManagedObjectContext *backgroundContext = //... create a new context for background insertion
dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(background, ^{ // If I comment this out, my test runs just fine
//... insert and update some entities
for (NSString *str in results) {
NSManagedObject *object = //...
}
});
}
}];
}
I'm currently testing this method with the following Kiwi code:
describe(#"MyAction", ^{
__block void (^completionBlock)(NSArray *array, NSError *error);
beforeEach(^{
// Stub the http client
id mockClient = [AFHTTPClient mock];
[WRNAPIClient stub:#selector(sharedClient) andReturn:mockClient];
// capture the block argument
KWCaptureSpy *spy = [mockClient captureArgument:#selector(fetchAllThingsWithCompletion:) atIndex:0];
[MyClass fetchSomeJSON]; // Call the method so we can capture the block
completionBlock = spy.argument;
// run the completion block
completionBlock(#[#"blah"], nil);
})
// If I remove the dispatch_async block, this test passes fine.
// If I add it in again the test fails, probably because its not waiting
it(#"should return the right count", ^{
// entityCount is a block that performs a fetch request count
NSInteger count = entityCount(moc, #"Task");
[[theValue(count) should] equal:theValue(4)];
})
// This works fine, but obviously I don't want to wait a second
it(#"should return the right count after waiting for a second", ^{
sleep(1);
NSInteger count = entityCount(moc, #"Task");
[[theValue(count) should] equal:theValue(4)];
});
};
If I remove the dispatch_async line, then I can get my test to run quickly. The only way I can get my test suite to run when using dispatch_async is to sleep(1) after calling the completion block. Using sleep() makes me think that I'm not approaching it in the right way. I have tried using shouldEventually but this doesn't seem to re-fetch my count value.
Have you tried these asynchronous block macros?
#define TestNeedsToWaitForBlock() __block BOOL blockFinished = NO
#define BlockFinished() blockFinished = YES
#define WaitForBlock() while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !blockFinished)
I have tried several approaches to solving this, none feel right.
1) Move the dispatch_async to its own class
+ (void)dispatchOnMainQueue:(Block)block
{
if ([NSThread currentThread] == [NSThread mainThread]) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
+ (void)dispatchOnBackgroundQueue:(Block)block
{
dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(background, block);
}
Then during test execution, swizzle the background dispatch to occur on the main queue. This worked, but was unpredictable. It also felt so wrong!
2) Move the setup code to Kiwi's beforeAll block, then sleep the main thread. This works as the Kiwi tests are run on the main thread, so we're effectively saying "let the background operations happen before carrying on with the tests". I think this is what I'm going to use. Yes it makes my unit tests run slower, but they pass when they should do, and fail when they should
describe(#"MyAction", ^{
__block void (^completionBlock)(NSArray *array, NSError *error);
beforeAll(^{
// Stub the http client
id mockClient = [AFHTTPClient mock];
[WRNAPIClient stub:#selector(sharedClient) andReturn:mockClient];
// capture the block argument
KWCaptureSpy *spy = [mockClient captureArgument:#selector(fetchAllThingsWithCompletion:) atIndex:0];
[WRNTaskImporter importAllTasksFromAPI];
completionBlock = spy.argument;
// run the completion block
completionBlock(#[#"blah"], nil);
// Wait for background import to complete
[NSThread sleepForTimeInterval:0.1];
})
// This works
it(#"should return the right count", ^{
// entityCount is a block that performs a fetch request count
NSInteger count = entityCount(moc, #"Task");
[[theValue(count) should] equal:theValue(4)];
})
};
The caveat of this approach is that it only works when you aren't changing any data before a test. Say for example I insert 4 entities, and want to check each entity was inserted as expected. This option would work here. If I needed to re-run the import method and check that the count hadn't increased, I would need to add another [NSThread sleepForTimeInterval:0.1] after calling the insertion code.
For normal block based Kiwi tests you should probably use either the expectFutureValue shouldEventually method, or KWCaptureSpy to test your code, but this may not help when calling nested blocks.
If anyone has a more appropriate method for testing cases like these I'm happy to hear it!
This is essentially what I'm doing to run an asynchronous method synchronously:
This essentially works when called once, but when called multiple times, it will eventually stay inside the while loop and never get signaled. Any ideas on how to set a timer to eventually time out after sometime?
__block SomeClass *result = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
if (!error) {
result = (SomeClass *)ResponseObject;
}
dispatch_semaphore_signal(semaphore);
}];
});
// wait with a time limit
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
dispatch_release(semaphore);
Thanks
That looks kind of like GCD abuse to me. ;) Are you running the run loop because this is executing on the main thread? Why not just use a dispatch_async() from your completion handler to invoke a handler on the main thread? eg:
- (void)handleDataReady: (id) results error: (NSError *) error {
// update your app
}
- (void)performAsyncUpdate {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self handleDataReady:responseObject error:error];
}];
});
}
If you really want to make it synchronous, i.e. blocking the calling thread until the operation completes then use the following pattern (of course you want to avoid blocking threads if possible)
NSCondition *waitCondtion = [NSCondition new];
__block BOOL completed = NO;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
if (!error) {
result = (SomeClass *)ResponseObject;
}
[waitCondtion lock];
completed = YES;
[waitCondition signal];
[waitCondition unlock];
}];
});
[waitCondtion lock];
if (!completed)
[waitCondtion wait];
[waitCondition unlock];
You can also use "waitUntilDate:" to timeout the wait after a period.
However, this pattern only works as long as the "someMethodWithCallback does not call its callback block on the same thread that is being blocked. I have copied your code because it is not obvious how "someMethodWithCallback" is implemented. Since this method is using an asynchronous pattern, then it must be doing something asynchronously therefore why are you calling it inside a dispatch_async? What thread will it call its callback block on?
You should "fill" the completion handler with whatever code you require to process the result when the completion handler finished (and also completely removing that run loop).
In order to "abort" an asynchronous operation, you should provide a cancel message which you send the asynchronous result provider.
In your case, since you have a singleton, the cancel message would have to be send like this:
[[SomeManager sharedInstance] cancel];
When the operation receives the cancel message, it should as soon as possible abort its task and call the completion handler with an appropriate NSError object indicating that it has been cancelled.
Note, that cancel messages may be asynchronous - that means, when it returns, the receiver may still execute the task.
You may achieve a "timeout" with setting up a timer, which sends the cancel message the operation, unless it has been invalidated when the operation finished.