Objective-C, cancel a dispatch queue using UI event - iphone

Scenario:
User taps a button asking for some kind of modification on address book.
A method is called to start this modification and an alert view is shown.
In order to show the alert view and keep the UI responsive, I used dispatch_queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
// Show the alert view
});
});
Start the process of address book modification using:
dispatch_async(modifyingAddressBookQueue, ^{});
Now, I want to provide the user with the ability to cancel the process anytime (of course before saving the address book). So when he taps the cancel button in the alert sheet, I want to access the dispatch block, set some certain BOOL to stop the process and revert the address book.
The problem is, you can't do that! you can't access the block and change any variable inside it since all variables are copied only once. Any change of variables inside the block while being executed won't be seen by the block.
To sum up: How to stop a going operation using a UI event?
Update:
The code for the process:
- (void) startFixingModification {
_fixContacts = YES;
__block BOOL cancelled = NO;
dispatch_queue_t modifyingAddressBookQueue;
modifyingAddressBookQueue = dispatch_queue_create(sModifyingAddressBookQueueIdentifier,
NULL);
dispatch_async(modifyingAddressBookQueue, ^{
for (NSMutableDictionary *contactDictionary in _contactArray) {
if (!cancelled) {
break;
}
i = i + 1;
BOOL didFixContact = [self fixNumberInContactDictionary:contactDictionary];
if (!didFixContact) {
_fixedNumbers = _fixedNumbers - 1;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
[self setAlertViewProgress:i];
});
});
}
});
cancelledPtr = &cancelled;
}
Code for alertview (my own lib) delegate
- (void) alertViewProgressCancel:(ASAlertViewProgress *)alertView { // This is a private lib.
if (cancelledPtr)
{
NSLog(#"stopping");
*cancelledPtr = YES;
}
}
In interface, I declare
BOOL* cancelledPtr;
Update 2:
It's getting really frustrating! for the following code
for (NSMutableDictionary *contactDictionary in _contactArray) {
NSLog(#"%d", _cancelModification);
if (_cancelModification) {
break;
}
}
if _cancelModification is set to YES, the for loop is broken and that's OK. Once I comment out the NSLog line, the _cancelModification is neglected when it changes to YES!

If you declare your BOOL using __block, then it can be changed outside of the block execution, and the block will see the new value. See the documentation for more details.
An example:
#interface SNViewController ()
{
BOOL* cancelledPtr;
}
#end
#implementation SNViewController
- (IBAction)start:(id)sender
{
__block BOOL cancelled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!cancelled) {
NSLog(#"running");
sleep(1);
}
NSLog(#"stopped");
});
cancelledPtr = &cancelled;
}
- (IBAction)stop:(id)sender
{
if (cancelledPtr)
{
NSLog(#"stopping");
*cancelledPtr = YES;
}
}
#end
Alternatively, use an ivar in your class to store the BOOL. The block will implicitly make a copy of self and will access the ivar via that. No need for __block.
#interface SNViewController ()
{
BOOL cancelled;
}
#end
#implementation SNViewController
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!cancelled) {
NSLog(#"running");
sleep(1);
}
NSLog(#"stopped");
});
}
- (IBAction)stop:(id)sender
{
NSLog(#"stopping");
cancelled = YES;
}
#end

Approach 1
Create a custom dispatch_async method that returns a "cancelable" block.
// The dispatch_cancel_block_t takes as parameter the "cancel" directive to suspend the block execution or not whenever the block to execute is dispatched.
// The return value is a boolean indicating if the block has already been executed or not.
typedef BOOL (^dispatch_cancel_block_t)(BOOL cancelBlock);
dispatch_cancel_block_t dispatch_async_with_cancel_block(dispatch_queue_t queue, void (^block)())
{
__block BOOL execute = YES;
__block BOOL executed = NO;
dispatch_cancel_block_t cancelBlock = ^BOOL (BOOL cancelled) {
execute = !cancelled;
return executed == NO;
};
dispatch_async(queue, ^{
if (execute)
block();
executed = YES;
});
return cancelBlock;
}
- (void)testCancelableBlock
{
dispatch_cancel_block_t cancelBlock = dispatch_async_with_cancel_block(dispatch_get_main_queue(), ^{
NSLog(#"Block 1 executed");
});
// Canceling the block execution
BOOL success1 = cancelBlock(YES);
NSLog(#"Block is cancelled successfully: %#", success1?#"YES":#"NO");
// Resuming the block execution
// BOOL success2 = cancelBlock(NO);
// NSLog(#"Block is resumed successfully: %#", success2?#"YES":#"NO");
}
Approach 2
Defining a macro for executing a block asynchronously if a condition is validated:
#define dispatch_async_if(queue,condition,block) \
dispatch_async(queue, ^{\
if (condition == YES)\
block();\
});
- (void)testConditionBlock
{
// Creating condition variable
__block BOOL condition = YES;
dispatch_async_if(dispatch_get_main_queue(), condition, ^{
NSLog(#"Block 2 executed");
});
// Canceling the block execution
condition = NO;
// Also, we could use a method to test the condition status
dispatch_async_if(dispatch_get_main_queue(), ![self mustCancelBlockExecution], ^{
NSLog(#"Block 3 executed");
});
}

Try to apply the following code sample to your situation:
__block UIView * tempView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 220, 30)];
[tempView setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:tempView];
[tempView release];
__block BOOL cancel = NO;
//点击之后就会开始执行这个方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
int i = 0;
while (i < 1000000000 && cancel == NO) {
i++;
}
NSLog(#"Task end: i = %d", i);
//这个不会执行,因为在之前,gcd task已经结束
[tempView removeFromSuperview];
});
//1s 之后执行这个方法
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"A GCD Task Start");
cancel = YES;
[tempView setBackgroundColor:[UIColor blackColor]];
});

Related

Stop a recursive loop into the method

Is possible stop a loop into a method call by selector?
I have created a new method for stop all running process, but I don't know how stop while loop in the audioloop method.
This is my code, please see:
-(id) init{
if((self=[super init]) ) {
// #property int isLoop;
self.isLoop = 1;
audioThread = [NSThread new];
[audioThread initWithTarget:self selector:#selector(audioLoop) object:nil];
[audioThread setThreadPriority:1];
[audioThread start];
}
}
// in this method i would stop while loop of audioLoop method
-(void)stopAllRunning{
self.isLoop = 0;
[audioThread cancel];
}
-(void) audioLoop{
while (isLoop) {
[_midiClock update];
}
}
Just add a condition and a break statement for doing this:
while (true)
{
if(yourCondition)
break;
else
[_midiClock update];
}
or Simply:
while (yourCondition)
{
[_midiClock update];
}

dispatch_async block not getting invoked

MySynchManager class is having a shared instance.
One of the function in MySynchManager class is
- (void)uploadSession:(NSString *)sessionId {
// run the upload process on a separate thread to not to block the main thread for user interaction
// process upload data in serial Queue
NSLog(#"Inside uploadSession");
if (!_serialQueue) {
NSLog(#"uploadSession, _serialQueue is NOT ACTIVE");
[self setRunLoopStarted:FALSE];
_serialQueue = dispatch_queue_create("sessionUploadQueue", NULL);
dispatch_async(_serialQueue, ^{
[[MySyncManager sharedInstance] dispatchSession:sessionId];
});
}
else {
//[self setRunLoopStarted:FALSE];
dispatch_async(_serialQueue, ^{
[self dispatchSession:sessionId];
});
NSLog(#"Adding block to the dispatch queue is complete");
}
}
uploadSession:#"session" is being called from view controllers.
The problem that I am facing is sometimes the code present in dispatchSession is called, but sometimes block is not called.
I only observe the log print statement after the block is printed.
Can any one of you explain the reason behind this?
This is weird code. Try this instead
-(void)uploadSession:(NSString *)sessionId
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_serialQueue = dispatch_queue_create("sessionUploadQueue", NULL);
});
dispatch_async(_serialQueue, ^{
[self dispatchSession:sessionId];
});
}

AVCaptureSession - Stop Running - take a long long time

I use ZXing for an app, this is mainly the same code than the ZXing original code except that I allow to scan several time in a row (ie., the ZXingWidgetController is not necesseraly dismissed as soon as something is detected).
I experience a long long freeze (sometimes it never ends) when I press the dismiss button that call
- (void)cancelled {
// if (!self.isStatusBarHidden) {
// [[UIApplication sharedApplication] setStatusBarHidden:NO];
// }
[self stopCapture];
wasCancelled = YES;
if (delegate != nil) {
[delegate zxingControllerDidCancel:self];
}
}
with
- (void)stopCapture {
decoding = NO;
#if HAS_AVFF
if([captureSession isRunning])[captureSession stopRunning];
AVCaptureInput* input = [captureSession.inputs objectAtIndex:0];
[captureSession removeInput:input];
AVCaptureVideoDataOutput* output = (AVCaptureVideoDataOutput*)[captureSession.outputs objectAtIndex:0];
[captureSession removeOutput:output];
[self.prevLayer removeFromSuperlayer];
/*
// heebee jeebees here ... is iOS still writing into the layer?
if (self.prevLayer) {
layer.session = nil;
AVCaptureVideoPreviewLayer* layer = prevLayer;
[self.prevLayer retain];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 12000000000), dispatch_get_main_queue(), ^{
[layer release];
});
}
*/
self.prevLayer = nil;
self.captureSession = nil;
#endif
}
(please notice that the dismissModalViewController that remove the view is within the delegate method)
I experience the freeze only while dismissing only if I made several scans in a row, and only with an iPhone 4 (no freeze with a 4S)
Any idea ?
Cheers
Rom
According to the AV Cam View Controller Example calling startRunning or stopRunning does not return until the session completes the requested operation. Since you are sending these messages to the session on the main thread, it freezes all the UI until the requested operation completes. What I would recommend is that you wrap your calls in an Asynchronous dispatch so that the view does not lock-up.
- (void)cancelled
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self stopCapture];
});
//You might want to think about putting the following in another method
//and calling it when the stop capture method finishes
wasCancelled = YES;
if (delegate != nil) {
[delegate zxingControllerDidCancel:self];
}
}

Making background thread to wait until another background thread gets completed in iphone

How to make a backgroundthread not to work until another background thread gets completed and how to make it to start it's thread once the first backgroundthread gets completed
Use flag to handle such type of event as shown below...
BOOL isYourthreadexecuting = NO;
- (void)beginThread {
isYourthreadexecuting = YES;
[self performSelectorInBackground:#selector(backgroundThread) withObject:nil];
}
- (void)backgroundThread {
[myClass performLongTask];
// Done!
isYourthreadexecuting = NO;
}
- (void)waitForThread {
if (! isYourthreadexecuting) {
// Thread completed
[self callyourmethod];
}
}
Edited >> Addition according use comment
I suggest you to use NSOperationQueue for multithreading.
Hope, this will you...
As stated in my comment, you can use GCD's Serial Dispatch Queues. Here is a sample code to demonstrate:
- (IBAction)buttonSerialQ2Pressed:(id)sender
{
dispatch_queue_t serialdQueue;
serialdQueue = dispatch_queue_create("com.mydomain.testbed.serialQ2", NULL);
dispatch_async(serialdQueue, ^{
//your code here
[self method1];
});
dispatch_async(serialdQueue, ^{
//your code here
[self method2];
});
dispatch_async(serialdQueue, ^{
//your code here
[self method2];
});
dispatch_async(serialdQueue, ^{
//your code here
[self method3];
});
}
-(void)method1
{
for (int i=0; i<1000; i++)
{
NSLog(#"method1 i: %i", i);
}
}
-(void)method2
{
for (int i=0; i<10; i++)
{
NSLog(#"method2 i: %i", i);
}
}
-(void)method3
{
for (int i=0; i<100; i++)
{
NSLog(#"method3 i: %i", i);
}
}

DSBezelActivityView not working

Here is a piece of code that I'm having problems with:
-(IBAction) nextRegister {
[DSBezelActivityView newActivityViewForView:self.view.superview];
previousPosition = (NSInteger *) currentPosition;
currentPosition = (NSInteger *) ((int)currentPosition + (int) 1 );
[currentRegistry updateOnServer];
[self loadNewRegisterInfos];
[DSBezelActivityView removeViewAnimated:YES];
}
Line by line, here is what this snippet does (or should do):
Show a DSBezelActivityView (from this: http://www.dejal.com/developer/dsactivityview)
Previous position receives actual position (since this will move to the next)
Next is current + 1;
Updates record in server (this takes ~1s)
Loads new record and renders on screen (this takes 1~3s, tops)
This line removes the DSBezelActivityView.
So, when I tap the button to load next record, it is supposed to show the ActivityView, load the data, then update the screen and release the ActivityView.
But what happens is: It freezes the app for 3s, shows and dismisses the activity view in less than a second and then finishes the routine.
What is wrong?
[Edited]
Resolved! Its a thread issue.
Split among 4 methods:
-(IBAction) nextRegister {
[NSThread detachNewThreadSelector:#selector(openActivityView) toTarget:self withObject:nil];
[self viewNextRegister];
[self removeView];
}
-(void)viewNextRegister{
previousPosition = (NSInteger *) currentPosition;
currentPosition = (NSInteger *) ((int)currentPosition + (int) 1 );
[currentRegister setScoreOnServer];
// [NSThread detachNewThreadSelector:#selector(loadRegisterInfo) toTarget:self withObject:nil];
[self loadRegisterInfo];
}
-(void)openActivityView{
[DSBezelActivityView newActivityViewForView:self.view.superview];
}
-(void)removeView {
[DSBezelActivityView removeViewAnimated:YES];
}
Rather than a separate thread which can cause issues if you aren't careful you can also do:
-(void) doNextRegister {
previousPosition = (NSInteger *) currentPosition;
currentPosition = (NSInteger *) ((int)currentPosition + (int) 1 );
[currentRegistry updateOnServer];
[self loadNewRegisterInfos];
[DSBezelActivityView removeViewAnimated:YES];
}
-(IBAction) nextRegister {
[DSBezelActivityView newActivityViewForView:self.view.superview];
[self performSelector:#selector(doNextRegister) withObject:nil afterDelay:0.0];
}