How to resume time out operations NSOperationQueue in iOS? - ios

I have already implemented NSOperationQueue successfully in application.
I have one operation queue which might have 1000 of NSOperations like below.
#interface Operations : NSOperation
#end
#implementation Operations
- (void)main
{
NSURL *url = [NSURL URLWithString:#"Your URL Here"];
NSString *contentType = #"application/json";
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request addValue:contentType forHTTPHeaderField: #"Content-Type"];
NSError *err = nil;
NSData *body = [NSJSONSerialization dataWithJSONObject:postVars options:NSJSONWritingPrettyPrinted error:&err];
[request setHTTPBody:body];
[request addValue:[NSString stringWithFormat:#"%lu", (unsigned long)body.length] forHTTPHeaderField: #"Content-Length"];
[request setTimeoutInterval:60];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *resData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}
#end
Now for that queue I am adding all 1000 operations at a time.
I add operation like below.
Operations *operation = [[Operations alloc]init];
[downloadQueue addOperation:operation];
Now what happens time interval is 60 as [request setTimeoutInterval:60]
So think like after 60 seconds if 300 operations out of 1000 operations is finished then other 700 operations are throwing request time out error.
So what should I do in this case.
Can I resume failed operations? Or I should again make operation and add it in queue.
Is there any better mechanism than this one?

My initial inclination would be, yes, just create new operations for the timed out requests and toss 'em back into the queue. It just feels simpler since you already have logic for adding those operations for your requests. HOWEVER:
Be careful to not get into sort of an infinite loop. If just ONE of those fails indefinitely for whatever reason, your queue will keep on chugging. I'd keep a failure count so that you know to stop retrying a request after some finite number of attempts.
If you KNOW that a large number will always fail, consider batching them in some fashion. One option would be a chain of dependencies. eg. You could try adding 200 of your ops, a "wait" NSOperation, the next 200, another "wait" NSOperation, another "wait" op, etc. Make the first wait op be dependent on those first 200 requests. Then make the next 200 depend on that first wait op, and so on:
[batch 1: first 200 requests]
[wait 1: op that waits for all of batch 1]
[batch 2: next 200, each waits for wait 1]
[wait 2: op that waits for all of batch 2]
[batch 3: next 200, each waits for wait 2]
etc.
Basically like 2 but instead of a "wait" op, have a "done" op. Lets say you have an array of 1000 requests to send: Toss 200 into the queue, then a "done" op which depends on those 200. When the "done" op runs (by definition, AFTER those 200 are done), it can then pull the next 200 from the array and toss them in (plus a new "done" op).
(maybe even consider making the wait/done op "pause" a few seconds to give the server a breather)
In other words, with #2 and #3, I'm saying "blast 200 at once, wait, then blast the next 200, wait, etc." (200 is arbitrary on my part, you know better than I as to what the best number is for your situation)

You can implement the NSURLConnectionDelegate method connection:didFailWithError: to check if the synchronous request failed, and if the error was that the request timed out. Then retry the connection in the delegate callback.
I should note that Apple strongly encourages use of NSURLSession over NSURLConnection, as many methods in NSURLConnection are now deprecated.

The first point is - don't let them all run at the same time! You can easily flood a mobile data connection if you try to run even 10 requests at the same time. So, before anything else, set the maxConcurrentOperationCount of the queue to something like 5.
This alone will likely massively reduce your issue and you might not need to do anything else.
You should also really look at using NSURLSession, and in this way you could also look at removing the operation queue and using HTTPMaximumConnectionsPerHost to control how many requests are made at the same time.
If you do still get a failure then one of the 'recursive type' options as other answers discuss is a workable solution. I'd add an attempt count to the operation subclass to make it easy to track and if it fails then check how many times it's failed and decide whether to create a new copy of the operation and add it to the queue or to raise an alert.

Related

Switched from sendSynchronousRequest deprecated to NSURLSession ,now app does not launch first activity

Previous code
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error: NULL];
return ([response statusCode] == 200) ? YES : NO;
Code using now
+(BOOL)isConnectNetwork{
NSString *urlString = #"http://www.google.com/";
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"HEAD"];
NSHTTPURLResponse *response;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
}];
[task resume];
}
i get this error
2020-11-06 13:07:36.125607+0000 App[8518:1786305] [NetworkInfo] Signal strength query returned error: Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied", descriptor: <CTServiceDescriptor 0x280000600, domain=1, instance=1>
2020-11-06 13:16:03.381478+0000 App[8518:1786223] Could not load IOSurface for time string. Rendering locally instead.
Doing this sort of network check is highly discouraged, because it is error-prone. It is usually much better to just assume the network is working until a real network request (something that you actually care about) fails, and then handle that failure appropriately (error message, background color change, whatever).
The reason not to do this is that on mobile devices, networks can work one second and not work one second later. So doing unnecessary network checks like this provides no benefit, but can potentially make your app less usable (particularly if you gate any features behind that check, which again, is highly discouraged).
My advice would be to just delete this method entirely, and just pretend that it always returns YES. In the long run, you'll be much happier with that approach. Handle errors when they happen. Don't try to seek them out.
That said, if you must do this for some reason, the problem you'll face is that NSURLSession tasks are asynchronous, but you're trying to use it synchronously. So to make that work, you would need to
Wrap all of this code with a dispatch_async block onto a different dispatch queue.
Outside of that block, block the thread that the method is running on so that the method won't return. You would typically do that by waiting on a semaphore.
Inside the completion block, set the value of a __block BOOL variable based on whether the request completed successfully or failed, then post to the semaphore to make the outer code stop waiting.
But again, that's highly discouraged. I've done this as a workaround in spots where there was no other way (involving swizzling code that I didn't control), but unless there's no other way, please don't do that. :-)

Running multiple background threads iOS

Is it possible to run multiple background threads to improve performance on iOS . Currently I am using the following code for sending lets say 50 network requests on background thread like this:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// send 50 network requests
});
EDIT:
After updating my code to something like this no performance gain was achieved :( Taken from here
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", NULL);
dispatch_group_t fetchGroup = dispatch_group_create();
// This will allow up to 8 parallel downloads.
dispatch_semaphore_t downloadSema = dispatch_semaphore_create(8);
// We start ALL our downloads in parallel throttled by the above semaphore.
for (NSURL *url in urlsArray) {
dispatch_group_async(fetchGroup, fetchQ, ^(void) {
dispatch_semaphore_wait(downloadSema, DISPATCH_TIME_FOREVER);
NSMutableURLRequest *headRequest = [NSMutableURLRequest requestWithURL:url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[headRequest setHTTPMethod: #"GET"];
[headRequest addValue: cookieString forHTTPHeaderField: #"Cookie"];
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[NSURLConnection sendAsynchronousRequest:headRequest
queue:queue // created at class init
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
// do something with data or handle error
NSLog(#"request completed");
}];
dispatch_semaphore_signal(downloadSema);
});
}
// Now we wait until ALL our dispatch_group_async are finished.
dispatch_group_wait(fetchGroup, DISPATCH_TIME_FOREVER);
// Update your UI
dispatch_sync(dispatch_get_main_queue(), ^{
//[self updateUIFunction];
});
// Release resources
dispatch_release(fetchGroup);
dispatch_release(downloadSema);
dispatch_release(fetchQ);
Be careful not to confuse threads with queues
A single concurrent queue can operate across multiple threads, and GCD never guarantees which thread your tasks will run on.
The code you currently have will submit 50 network tasks to be run on a background concurrent queue, this much is true.
However, all 50 of these tasks will be executed on the same thread.
GCD basically acts like a giant thread pool, so your block (containing your 50 tasks) will be submitted to the next available thread in the pool. Therefore, if the tasks are synchronous, they will be executed serially. This means that each task will have to wait for the previous one to finish before preceding. If they are asynchronous tasks, then they will all be dispatched immediately (which begs the question of why you need to use GCD in the first place).
If you want multiple synchronous tasks to run at the same time, then you need a separate dispatch_async for each of your tasks. This way you have a block per task, and therefore they will be dispatched to multiple threads from the thread pool and therefore can run concurrently.
Although you should be careful that you don't submit too many network tasks to operate at the same time (you don't say specifically what they're doing) as it could potentially overload a server, as gnasher says.
You can easily limit the number of concurrent tasks (whether they're synchronous or asynchronous) operating at the same time using a GCD semaphore. For example, this code will limit the number of concurrent operations to 6:
long numberOfConcurrentTasks = 6;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(numberOfConcurrentTasks);
for (int i = 0; i < 50; i++) {
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self doNetworkTaskWithCompletion:^{
dispatch_semaphore_signal(semaphore);
NSLog(#"network task %i done", i);
}];
});
}
Edit
The problem with your code is the line:
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", NULL);
When NULL is passed to the attr parameter, GCD creates a serial queue (it's also a lot more readable if you actually specify the queue type here). You want a concurrent queue. Therefore you want:
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", DISPATCH_QUEUE_CONCURRENT);
You need to be signalling your semaphore from within the completion handler of the request instead of at the end of the request. As it's asynchronous, the semaphore will get signalled as soon as the request is sent off, therefore queueing another network task. You want to wait for the network task to return before signalling.
[NSURLConnection sendAsynchronousRequest:headRequest
queue:queue // created at class init
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
// do something with data or handle error
NSLog(#"request completed");
dispatch_semaphore_signal(downloadSema);
}];
Edit 2
I just noticed you are updating your UI using a dispatch_sync. I see no reason for it to be synchronous, as it'll just block the background thread until the main thread has updated the UI. I would use a dispatch_async to do this.
Edit 3
As CouchDeveloper points out, it is possible that the number of concurrent network requests might be being capped by the system.
The easiest solution appears to be migrating over to NSURLSession and configuring the maxConcurrentOperationCount property of the NSOperationQueue used. That way you can ditch the semaphores altogether and just dispatch all your network requests on a background queue, using a callback to update the UI on the main thread.
I am not at all familiar with NSURLSession though, I was only answering this from a GCD stand-point.
You can send multiple requests, but sending 50 requests in parallel is usually not a good idea. There is a good chance that a server confronted with 50 simultaneous request will handle the first few and return errors for the rest. It depends on the server, but using a semaphore you can easily limit the number of running requests to anything you like, say four or eight. You need to experiment with the server in question to find out what works reliably on that server and gives you the highest performance.
And there seems to be a bit of confusion around: Usually all your network requests will run asynchronously. That is you send the request to the OS (which goes very quick usually), then nothing happens for a while, then a callback method of yours is called, processing the data. Whether you send the requests from the main thread or from a background thread doesn't make much difference.
Processing the results of these requests can be time consuming. You can process the results on a background thread. You can process the results of all requests on the same serial queue, which makes it a lot easier to avoid multithreading problems. That's what I do because it's easy and even in the worst case uses one processor for intensive processing of the results, while the other processor can do UI etc.
If you use synchronous network requests (which is a bad idea), then you need to dispatch each one by itself on a background thread. If you run a loop running 50 synchronous network requests on a background thread, then the second request will wait until the first one is completely finished.

Why does an asynchronous NSURLConnection make the UI sluggish on iOS?

I noticed that the framerate while scrolling a collection view on an iPhone 4 dropped significantly (at times to 5 FPS) when a download using NSURLConnection was taking place in the background. I first suspected AFNetworking to be the culprit, but it turns out that the same thing happens when I simply use a block:
- (void)startBlockDownload:(id)sender
{
NSLog(#"starting block download");
dispatch_queue_t defQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
void (^downloadBlock) (void);
downloadBlock = ^{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:_urlString]];
NSURLResponse *response = nil;
NSError *error = nil;
NSData* result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"block request done");
};
dispatch_async(defQueue, downloadBlock);
}
What gives? Is a background download so demanding that it renders the UI extremely sluggish? Is it the slow flash memory? Is there anything that can be done to keep the UI very responsive while doing a background download?
I've created a sample project to demonstrate the issue: https://github.com/jfahrenkrug/AFNetworkingPerformanceTest
Also see this issue on AFNetworking that I have started about the topic: https://github.com/AFNetworking/AFNetworking/issues/1030#issuecomment-18563005
Any help is appreciated!
As the comments you linked to say: The iPhone 4 is still a single-core machine so no matter how much "multitasking" you do, it will still only be executing one set of code at once. What's worse is that the sendSynchronousRequest and the UI code are both blocking sets of code, so they will take up the maximum amount of time allotted to them by the OS and then the OS will perform an expensive context switch to prepare to execute the other.
You can try two things:
1) Use the async API of NSURLConnection instead of dispatching away a synch request (though I have the feeling you tried this with AFNetworking).
2) Lower the priority of the queue you dispatch to (preferably to DISPATCH_QUEUE_PRIORITY_BACKGROUND if possible). This might give the main queue more cycles to execute on, but it may not since the entire operation is just one big chunk.
If those both fail to work, then it probably simply is too much for a single-core processor to handle (scrolling is not a light operation, and downloading requires constant running time to receive data). That's my best guess anyway...

Making threaded NSURLConnections

I have a lot of connections going when my app starts, so I wanna put them on background threads so I can make new connections other than the starting connections before they all complete.
Below, threadedRequest: is a method that's starting a NSURLConnection, but when I call performSelectorInBackground:withObject: in the if clause, the connection starts, but never finishes. The else clause works fine and returns data from the connection
if (background)
{
[self performSelectorInBackground: #selector(threadedRequest:) withObject: args];
}
else
{
[self performSelector: #selector(threadedRequest:) onThread: [NSThread mainThread] withObject: args waitUntilDone: NO];
}
If you want to perform NSURLConnection in the background, you must be sensitive to special conditions that apply to NSURLConnectionDataDelegate methods in background threads. You have several options:
Use one of the non-delegate alternatives. Given that you're performing this in the background, you could use sendSynchronousRequest, or if just requesting data from a simple URL, you could use NSData class method dataWithURL.
If you really need the delegate version (because you need progress updates via didReceiveData or because you need one of the NSURLConnectDelegate methods for authentication or the like, you have a couple of basic options:
You can create a NSOperationQueue and set the delegate queue for the connection. For example, rather than:
- (void)startConnection:(NSURL *)url
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
}
You could do:
- (void)startConnection:(NSURL *)url
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection setDelegateQueue:self.connectionDelegateQueue];
[connection start];
}
Alternatively, you could do what AFNetworking does (see AFURLConnectionOperation.m, for sample implementation):
Create a dedicated NSThread for the NSURLConnectionDataDelegate calls;
Start a NSRunLoop on that thread;
Use the same startImmediately:NO rendition of the NSURLConnection init method as above; and
Use the scheduleInRunLoop option for the NSURLConnection before you start it.
Or, easiest, you could just use AFNetworking
Two observations:
I might be inclined to use operation or dispatch queues instead of performSelectorInBackground. See Concurrency Programming Guide.
By the way, you should be aware that there is a limit as to how many concurrent NSURLConnection requests iOS can perform simultaneously (it's 5 or 6, I believe). Thus, if you initiate a dozen background requests and then initiate a "foreground" request, the "foreground" request may not start immediately. You may want to limit how many background requests that can operate concurrently if you want to avoid this potential problem.
Personally, when I want to constrain the number of network requests, I add my background requests to a NSOperationQueue and set the number of maximum concurrent operations (e.g. I generally use 4), enjoying the benefits of concurrent operations, while not trying to perform more simultaneous connections than iOS will permit. By the way, when availing yourself of operation queue's maxConcurrentOperations feature, you have to make sure that the requests are synchronous (or wrap the asynchronous connection in a custom NSOperation that won't terminate until the connection is done or fails).

Queuing NSURLRequest to simulate a synchronous, blocking request

I am interacting with a web-controlled hardware device. You send it a request via a URL (e.g., http://device/on?port=1 or http://device/off?port=3) to turn stuff on and off, and it sends back "success" or "failure". It is a simple device, however, so while it's processing a request --- i.e., until it returns the status of the request that it's processing --- it will ignore all subsequent requests. It does not queue them up; they just get lost.
So I need to send serial, synchronous requests. I.e., req#1, wait for response#1, req#2, wait for response#2, req#3, wait for response #3, etc.
Do I need to manage my own thread-safe queue of requests, have the UI thread push requests into one end of the queue, and have another thread pull the requests off, one at a time, as soon as the previous one either completes or times out, and send the results back to the UI thread? Or am I missing something in the API that already does this?
Thanks!
...R
What should work is to use an NSOperationQueue instance, and a number of NSOperation instances that perform the various URL requests.
First, set up a queue in the class that will be enqueueing the requests. Make sure to keep a strong reference to it, i.e.
#interface MyEnqueingClass ()
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#end
Somewhere in the implementation, say the init method:
_operationQueue = [[NSOperationQueue alloc] init];
_operationQueue.maxConcurrentOperationCount = 1;
You want basically a serial queue, hence the maxConcurrentOperationCount of 1.
After setting this up, you'll want to write some code like this:
[self.operationQueue addOperationWithBlock:^{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"my://URLString"]];
NSError *error;
NSURLResponse *response;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!responseData)
{
//Maybe try this request again instead of completely restarting? Depends on your application.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//Do something here to handle the error - maybe you need to cancel all the enqueued operations and start again?
[self.operationQueue cancelAllOperations];
[self startOver];
}];
}
else
{
//Handle the success case;
}
}];
[self.operationQueue addOperationWithBlock:^{
//Make another request, according to the next instuctions?
}];
In this way you send synchronous NSURLRequests and can handle the error conditions, including by bailing out completely and starting all over (the lines with -cancelAllOperations called). These requests will be executed one after the other.
You can also of course write custom NSOperation subclasses and enqueuing instances of those rather than using blocks, if that serves you.
Hope this helps, let me know if you have any questions!
You can use NSOperationQueue class and also use some API which are build in on it for example AFNetworking.

Resources