Async Network Operations Never Finish

Async network operations never finish

So i've made an extension method on IDisposable that creates a CancellationToken that disposes the connection on timeout, so the task finishes and everything carries on:

public static IDisposable CreateTimeoutScope(this IDisposable disposable, TimeSpan timeSpan)
{
var cancellationTokenSource = new CancellationTokenSource(timeSpan);
var cancellationTokenRegistration = cancellationTokenSource.Token.Register(disposable.Dispose);
return new DisposableScope(
() =>
{
cancellationTokenRegistration.Dispose();
cancellationTokenSource.Dispose();
disposable.Dispose();
});
}

And the usage is extremely simple:

try
{
var client = new UdpClient();
using (client.CreateTimeoutScope(TimeSpan.FromSeconds(2)))
{
var result = await client.ReceiveAsync();
// Handle result
}
}
catch (ObjectDisposedException)
{
return null;
}

Extra Info:

public sealed class DisposableScope : IDisposable
{
private readonly Action _closeScopeAction;
public DisposableScope(Action closeScopeAction)
{
_closeScopeAction = closeScopeAction;
}
public void Dispose()
{
_closeScopeAction();
}
}

Async task does not end

This is a standard deadlock condition you get when you start an async operation and then block on the returned task.

Here is a blog post discussion the topic.

Basically, the await call ensures that the continuation it wires up of the task will run in the context you were originally in (which is very helpful) but because you are calling Wait in that same context it's blocking, so the continuation never runs, and that continuation needs to run for the wait to end. Classic deadlock.

As for the fix; usually it means you just shouldn't be doing a blocking wait on the async operation; it's contrary to the design of the whole system. You should, "async all the way up". In this case it would mean that GetPageData should return a Task<string> rather than a string, and rather than waiting on the other operations that return a task you should await on them.

Now, having said that, there are ways of doing a blocking wait on the async operations without deadlocking. While it can be done, it honestly defeats the purpose of using async/await in the first place. The primary advantage of using that system is that the main context isn't blocked; when you block on it that entire advantage goes away, and you might as well just use blocking code all the way through. async/await is really more of an all-or-nothing paradigm.

Here is how I would structure that class:

public class PageDownloader
{
private System.Net.Http.HttpClient _client;
private Encoding _encoding;

public PageDownloader()
: this(Encoding.UTF8) { }

public PageDownloader(Encoding encoding)
{
_encoding = encoding;
_client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) };
}

public async Task<string> GetPageData(string link)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, link);
request.Headers.Add("User-Agent", "Chrome/21.0.1180.89");
request.Headers.Add("Accept", "text/html");

HttpResponseMessage response = await _client.GetAsync(request.RequestUri);

return await response.Content.ReadAsStringAsync(); ;
}
}

Check when multiple asynchronous network operations are completed

Keep a count of how many active requests are in progress using an instance variable. This could be a simple NSInteger which is incremented and decremented, or it could be a dictionary, keyed by the user name, and holding a number or more detailed tracking record if required. It really depends what level of detail you need. The class which makes the API calls should do all of the management of this record data and just offer 'count' methods.

Waiting for several networking async calls in iOS

There are probably lots of ways you could do this. First, I don't think there's any need to use GCD in the view controller -- loader is already doing things asynchronously, so the creation of loader is fast.

As for how Loader knows when all its network operations are done, you could just keep a list of strings in a mutable array, like "1 done", "2 done", etc. that would be the same as strings sent in the user info of the notifications called in connectionDidFinishLoading:. All the services could send the same notification, but with different user info. In the selector for the observer, remove the string identical to the one in the user info, and check if the array is empty -- when it is, all your services are done. At that point, I would use a delegate method to pass back the data to the view controller. Something like this in Loader:

- (void)viewDidLoad {
[super viewDidLoad];
self.doneStrings = [@[@"1 done", @"2 done", @"3 done", @"4 done"] mutableCopy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationReceived:) name:@"SeriveFinishedNotification" object:nil];
}

-(void)notificationReceived:(NSNotification *) aNote {
[self.doneStrings removeObjectIdenticalTo:[aNote.userInfo objectForKey:@"doneString"]];
if (self.doneStrings.count == 0)
[delegate doSomethingWithTheData: theData];
}

You would probably need to some other things like handle the case where some of the network operations fail.

Can asynchronous operations be used with `progress` on OperationQueue?

You are combining two different but related concepts; asynchronous and concurrency.

An OperationQueue always dispatches Operations onto a separate thread so you do not need to make them explicitly make them asynchronous and there is no need to override start(). You should ensure that your main() does not return until the operation is complete. This means blocking if you perform asynchronous tasks such as network operations.

It is possible to execute an Operation directly. In the case where you want concurrent execution of those operations you need to make them asynchronous. It is in this situation that you would override start()

If you want to implement a concurrent operation—that is, one that runs asynchronously with respect to the calling thread—you must write additional code to start the operation asynchronously. For example, you might spawn a separate thread, call an asynchronous system function, or do anything else to ensure that the start method starts the task and returns immediately and, in all likelihood, before the task is finished.

Most developers should never need to implement concurrent operation objects. If you always add your operations to an operation queue, you do not need to implement concurrent operations. When you submit a nonconcurrent operation to an operation queue, the queue itself creates a thread on which to run your operation. Thus, adding a nonconcurrent operation to an operation queue still results in the asynchronous execution of your operation object code. The ability to define concurrent operations is only necessary in cases where you need to execute the operation asynchronously without adding it to an operation queue.

In summary, make sure your operations are synchronous and do not override start if you want to take advantage of progress

Update

While the normal advice is not to try and make asynchronous tasks synchronous, in this case it is the only thing you can do if you want to take advantage of progress. The problem is that if you have an asynchronous operation, the queue cannot tell when it is actually complete. If the queue can't tell when an operation is complete then it can't update progress accurately for that operation.

You do need to consider the impact on the thread pool of doing this.

The alternative is not to use the inbuilt progress feature and create your own property that you update from your tasks.

How can I set a timeout for an Async function that doesn't accept a cancellation token?

While you can rely on WithCancellation for reuse purposes, a simpler solution for a timeout (which doesn't throw OperationCanceledException) would be to create a timeout task with Task.Delay and wait for the first task to complete using Task.WhenAny:

public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult), TaskContinuationOptions.ExecuteSynchronously);
return Task.WhenAny(task, timeoutTask).Unwrap();
}

Or, if you want to throw an exception in case there's a timeout instead of just returning the default value (i.e. null):

public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
if (task == await Task.WhenAny(task, Task.Delay(timeout)))
{
return await task;
}
throw new TimeoutException();
}

And the usage would be:

var content = await Response.Content.ReadAsStringAsync().WithTimeout(TimeSpan.FromSeconds(1));

Can I use async WCF services to avoid network timeout problems?

There are no callbacks at the network level. Async IO is all about not blocking threads on a single machine. Both communication parties can independently decide to use sync or async IO as they please. Neither party can even detect what the other chose.

You are using the SOAP-based basicHttpBinding. SOAP has no notion of asynchronous invocation. Neither does any of the other bindings.

Async IO can do nothing to resolve your timeout problem. Those timeouts are caused by something on the network (as you said). The network has nothing to do with how a service is implemented. It cannot even find out and respond to it.



Related Topics



Leave a reply



Submit