I have designed an application which sometimes needs to make intensive computing (inside a loop), that loads cpu heavily during few tenths of seconds. I launch a UIAlertView to display a message, something like: "please wait for few seconds".
My problem is that during this period the App is not responsive at all, and the UIAlertView itself cannot be dismissed by user. This is not a major issue but not fair for the user. But it could become a real problem if I was to implement some kind of cancel button.
How can I solve this ? For example is there some sleep command that I could use inside my computing loop when detecting too much cpu load ?
Thanks.
You shouldn't perform CPU intensive operations on the main thread - as it will impact the app responsiveness as you have seen. You can use a dispatch queue to perform the task on another thread.
There is more detail in the Apple guide I linked to, but in general you can use something like -
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^{
[self performIntensiveTask];
});
If necessary, you may need to know when your intensive task has completed. You could use an NSNotification to do this or you could just update your UI elements - if you choose the second be aware that you should only update UI elements on the main thread so you would have -
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^{
[self performIntensiveTask];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
});
Related
How can I run a loop in the background such as:
while(1==1){
NSLog(#"hello");
}
while being able to detect a button click such as:
- (IBAction)button:(id)sender {
//do something
}
You can use GCD to run the code on a background thread.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
});
Read Apple's Concurrency Programming Guide and maybe Threading Programming Guide.
The first of these will introduce you to operation queues (NSOperation) and dispatch queues (GCD), the second to threads (NSThread & Posix).
If after reading the guide you are unsure which of the approaches to take, at least for the situation you raise above, consider GCD first and then operation queues.
If you get stuck implementing your solution ask a new question, showing your code and explaining your issues. Somebody will undoubtedly help you out.
HTH
The problem:
When lazy-loading a list of 100+ icons in the background, I hit the GCD thread limit (64 threads), which causes my app to freeze up with a semaphore_wait_trap on the main thread. I want to restructure my threading code to prevent this from happening, while still loading the icons asynchronous to prevent UI blocking.
Context:
My app loads a screen with SVG icons on it. The amount differs on average from 10-200. The icons get drawn by using a local SVG image or a remote SVG image (if it has a custom icon), then they get post-processed to get the final image result.
Because this takes some time, and they aren't vital for the user, I want to load and post-process them in the background, so they would pop in over time. For every icon I use the following:
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
//code to be executed in the background
SVGKImage *iconImage = [Settings getIconImage:location];
dispatch_async(dispatch_get_main_queue(), ^{
//code to be executed on the main thread when background task is finished
if (iconImage) {
[iconImgView setImage:iconImage.UIImage];
}
});
});
The getIconImage method handles the initial loading of the base SVG, which reads it synchronized with [NSInputStream inputStreamWithFileAtPath:path] if local, and [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&errorWithNSData] if it should load remotely. This all happens synchronous.
Then there is some post-processing of recoloring the SVG, before it gets returned and put in the UIImageView on the main thread.
Question:
Is there a way to structure my code to allow for parallelized background loading but prevent deadlock because of too many threads?
Solution EDIT:
_iconOperationQueue = [[NSOperationQueue alloc]init];
_iconOperationQueue.maxConcurrentOperationCount = 8;
// Code will be executed on the background
[_iconOperationQueue addOperationWithBlock:^{
// I/O code
SVGKImage *baseIcon = [Settings getIconBaseSVG:location];
// CPU-only code
SVGKImage *iconImage = [Settings getIconImage:location withBaseSVG:baseIcon];
UIImage *svgImage = iconImage.UIImage; // Converting SVGKImage to UIImage is expensive, so don't do this on the main thread
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Code to be executed on the main thread when background task is finished
if (svgImage) {
[iconImgView setImage:svgImage];
}
}];
}];
Instead of directly using GCD with a concurrent queue, use an NSOperationQueue. Set its maxConcurrentOperationCount to something reasonable, like 4 or 8.
If you can, you should also separate I/O from pure computation. Use the width-restricted operation queue for the I/O. The pure computation you can use an unrestricted operation queue or pure GCD for.
The reason is that I/O blocks. GCD detects that the system is idle and spins up another worker thread and starts another task from the queue. That blocks in I/O, too, so it does that some more until it hits its limit. Then, the I/O starts completing and the tasks unblock. Now you have oversubscribed the system resources (i.e. CPU) because there are more tasks in flight than cores and suddenly they are actually using CPU instead of being blocked by I/O.
Pure computation tasks don't provoke this problem because GCD sees that the system is actually busy and doesn't dequeue more tasks until earlier ones have completed.
You can stay with GCD by using a semaphore something like this running the whole operation in the background otherwise waiting for the semaphore will stall the UI:
dispatch_semaphore_t throttleSemaphore = dispatch_semaphore_create(8);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for /* Loop through your images */ {
dispatch_semaphore_wait(throttleSemaphore, DISPATCH_TIME_FOREVER);
dispatch_async(concurrentQueue, ^{
//code to be executed in the background
SVGKImage *iconImage = [Settings getIconImage:location];
dispatch_async(dispatch_get_main_queue(), ^{
//code to be executed on the main thread when background task is finished
if (iconImage) {
[iconImgView setImage:iconImage.UIImage];
}
dispatch_semaphore_signal(throttleSemaphore);
});
});
}
I want to call a web service to upload some image data to the server. I have to send the data 5 times to the server. This piece of code is written in a function which is called after 10 seconds duration by a timer. Now the problem is that the response of Web service might be late and second call to web service might initiate. I want to keep them in queue so that when one finishes other is called. I think I am not going in right way. I just want to maintain a queue in which I can call the web service multiple times and make different async calls to the server. Basically how I could call multiple async tasks.
Any help will be appreciated.
dispatch_queue_t myQueue;
myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
[self uploadDataToServer];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
});
});
A simple way is to keep a counter and recurse. It looks like your uploadToServer is a blocking call so e.g.
- (void)uploadDataToServerAndRepeat:(NSUInteger)repeatCount {
if(repeatCount)
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0),
^{
[self uploadDataToServer];
[self uploadDataToServerAndRepeat:repeatCount - 1];
// dispatch async to main queue for UI update, too
});
}
// ... and, to start things off ...
[self uploadDataToServerAndRepeat:5];
Added a sample project. Maybe there was any other way for it but you can simply use TaskQueue.m class in sample process. You can modify it if you wish. https://github.com/kocakmstf/AsyncTaskQueue
I have an app that allows users to obtain stream gauge info via web service. In rural areas, network connections may be slow or nonexistent. I would like to set a timeout on the fetch operation but am not really sure how to go about it. Here is the fetch operation:
[self.view addSubview:hud];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[hud removeFromSuperview];
});
});
If I wanted this operation to timeout (10 seconds), causing the activity indicator to disappear and return the app to normal functioning, how might I go about that?
Thanks!
blocks can't be canceled, they need to check themselves.
the bad & hard way (DYI)
you need a separate queue that just has a timeoutblock end where, on timeout you set a BOOL to isCancel. In the other block you check
the GOOD & easy way
use NSOperationQueue & NSOperation APIs -- :)
There, NSOperationQueue also won't kill long running operations automatically but the general infrastructure is in place and it is easier to accomplish
e.g. the operation could do it on its own... see AFNetworkingOperations for one way of doing it
I have a GCD that goes on the background. I have a button that when pressed I want it to load a loading wait screen while the GCD finishes, and then execute the rest of the code on that button. Attached is the sample.
Mine does not work, I basically want to say, wait as long as it takes to finish GCD, and load a waiting message in the meantime, when done continue code.
Thank you
- (IBAction)btnTapped:(id)sender
{
shouldCancel=NO;
dispatch_queue_t existingQueque = dispatch_get_main_queue();//finds the current GCD, the one I created in a different method
dispatch_group_t group =dispatch_group_create();
dispatch_group_async(group, existingQueque, ^
{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//does not work, I guess group can't be created here.
[self performSelectorOnMainThread:#selector(showWaitViewWithMessage:) withObject:#"Loading" waitUntilDone:YES];//load this until GCD queque done
[self performSelector:#selector(performSearch) withObject:nil afterDelay:0];
});
}
A couple of thoughts:
You suggest that dispatch_get_main_queue() "finds the current GCD, the one I created in a different method". No, this just gets the main queue (the one that, if you use it, will block your user interface), not the queue that you created elsewhere through dispatch_create_queue. The dispatch_get_main_queue() just gets the main queue, and while your searching is happening, your UI will be blocked (e.g. UIActivityIndicatorView won't spin, whatever).
If you've dispatched a whole bunch of tasks to a background queue, if you want to wait for all of them to finish, that's when you use dispatch_group_t or dispatch_barrier, but given what you've shown doesn't require that (you have only one dispatched operation), you just don't need to go there. By the way, barriers are not recommended if you're using global queues.
The typical pattern for a single GCD background task is more simple than your question suggests. You (a) update your UI to say "loading" and show a UIActivityIndicatorView or something like that, so the user has a richer UX showing them that the app is working on something; (b) dispatch the search in the background; and (c) when done, dispatch the UI update back to the main queue. Thus, the typical pattern is:
- (IBAction)btnTapped:(id)sender
{
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// or, if you've already created you own background queue just use that here,
// or just create one here. But don't use dispatch_get_main_queue, as that
// won't use a background queue.
//
// dispatch_queue_t backgroundQueue = dispatch_queue_create("org.yourdomain.yourapp.search", NULL);
[self showWaitViewWithMessage:#"Loading"];
dispatch_async(backgroundQueue, ^{
[self performSearch]; // do this in the background
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUiAfterSearch]; // when done, dispatch UI update back to main queue
});
});
// if you created a queue, remember to release it
//
// dispatch_release(backgroundQueue);
}
As an aside, in your performSelectorOnMainThread, I see no reason to waitUntilDone. Don't wait unless there is some compelling reason to do so. As you see above, this construct isn't needed at all, but just a FYI.
By the way, it's important to know that many servers impose limits on how many concurrent requests a given client may make at a time. If it's possible that you might be initiating multiple requests (e.g. the user taps buttons and the server is slow to respond) and this allows them to run concurrently. In this scenario, it's worth pursuing NSOperationQueue, where you can set maxConcurrentOperationCount. If you use the block versions of the NSOperationQueue methods (e.g. addOperationWithBlock rather than GCD's dispatch_async), the code can be structured in the same way, but it let's you constrain the number of background operations.
Also, NSOperationQueue offers the ability to easily establish dependencies between the operations (e.g. a completion NSOperation that is dependent on all of the others finishing). I can outline that, but the code you posted doesn't necessitate that, so I'll spare you that unless you let me know you want to see what that would look like.
you have to save the queue you create, dont create it each time and if you only want one at a time, use a serial queue
#implementation DDAppDelegate {
dispatch_queue_t queue;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self do];
[self performSelector:#selector(do) withObject:nil afterDelay:1];
}
- (void)do {
if(!queue)
queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
//serialized
NSLog(#"1");
sleep(10);
});
}
#end
if you want a concurrent queue, use a global queue and dispatch_barrier_async
#implementation DDAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self do];
[self performSelector:#selector(do) withObject:nil afterDelay:1];
}
- (void)do {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_barrier_async(queue, ^{
//serialized
NSLog(#"1");
sleep(10);
});
}