appending timeouts to asynchronous requests in WinRT - c#

I'm using this piece of code to append a TimeOut to a request call that I'm making to ensure that we don't encounter a slow internet connection - and that its handled appropriately.
When I DO meet the timeout condition I get an error message that I've been unable to fix within the extended function.
How to go about this task in general?
Code:
public static class SOExtensions
{
public static Task<T> WithTimeout<T>(this Task<T> task, TimeSpan duration)
{
return Task.Factory.StartNew(() =>
{
bool b = task.Wait(duration);
if (b) return task.Result;
return default(T);
});
}
}
Usage:
var response = await HttpWebRequest
.Create(request)
.GetResponseAsync()
.WithTimeout(TimeSpan.FromSeconds(1));
I handle AggregateException with this (and through a WebException), but it still generates AggregateException when the timeout fails.

I recommend using CancellationToken for timeouts (new CancellationTokenSource(myTimeSpan).Token), which you can then use within your async methods (e.g., for HttpClient, you pass the token to the GetAsync method). This takes up fewer resources because the HTTP request itself is canceled.
But if you want an "external" timeout, a more natural approach would be this:
public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan duration)
{
var timeout = Task.Delay(duration);
var completeTask = await Task.WhenAny(task, timeout);
if (completeTask == timeout)
return default(T);
else
return await task;
}
This will prevent the exceptions from being wrapped into an AggregateException. It's not as efficient as the CancellationToken approach, though, which would look like this:
var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1));
var response = await new HttpClient().GetAsync(uri, timeout.Token);
Note that the CancellationToken approach will throw OperationCanceledException instead of returning a default(T) if there is a timeout.

Related

HttpClient.GetAsync fails after running for some time with TaskCanceledException "A task was cancelled" [duplicate]

It works fine when have one or two tasks however throws an error "A task was cancelled" when we have more than one task listed.
List<Task> allTasks = new List<Task>();
allTasks.Add(....);
allTasks.Add(....);
Task.WaitAll(allTasks.ToArray(), configuration.CancellationToken);
private static Task<T> HttpClientSendAsync<T>(string url, object data, HttpMethod method, string contentType, CancellationToken token)
{
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(method, url);
HttpClient httpClient = new HttpClient();
httpClient.Timeout = new TimeSpan(Constants.TimeOut);
if (data != null)
{
byte[] byteArray = Encoding.ASCII.GetBytes(Helper.ToJSON(data));
MemoryStream memoryStream = new MemoryStream(byteArray);
httpRequestMessage.Content = new StringContent(new StreamReader(memoryStream).ReadToEnd(), Encoding.UTF8, contentType);
}
return httpClient.SendAsync(httpRequestMessage).ContinueWith(task =>
{
var response = task.Result;
return response.Content.ReadAsStringAsync().ContinueWith(stringTask =>
{
var json = stringTask.Result;
return Helper.FromJSON<T>(json);
});
}).Unwrap();
}
There's 2 likely reasons that a TaskCanceledException would be thrown:
Something called Cancel() on the CancellationTokenSource associated with the cancellation token before the task completed.
The request timed out, i.e. didn't complete within the timespan you specified on HttpClient.Timeout.
My guess is it was a timeout. (If it was an explicit cancellation, you probably would have figured that out.) You can be more certain by inspecting the exception:
try
{
var response = task.Result;
}
catch (TaskCanceledException ex)
{
// Check ex.CancellationToken.IsCancellationRequested here.
// If false, it's pretty safe to assume it was a timeout.
}
I ran into this issue because my Main() method wasn't waiting for the task to complete before returning, so the Task<HttpResponseMessage> was being cancelled when my console program exited.
C# ≥ 7.1
You can make the main method asynchronous and await the task.
public static async Task Main(){
Task<HttpResponseMessage> myTask = sendRequest(); // however you create the Task
HttpResponseMessage response = await myTask;
// process the response
}
C# < 7.1
The solution was to call myTask.GetAwaiter().GetResult() in Main() (from this answer).
var clientHttp = new HttpClient();
clientHttp.Timeout = TimeSpan.FromMinutes(30);
The above is the best approach for waiting on a large request.
You are confused about 30 minutes; it's random time and you can give any time that you want.
In other words, request will not wait for 30 minutes if they get results before 30 minutes.
30 min means request processing time is 30 min.
When we occurred error "Task was cancelled", or large data request requirements.
Another possibility is that the result is not awaited on the client side. This can happen if any one method on the call stack does not use the await keyword to wait for the call to be completed.
Promoting #JobaDiniz's comment to an answer:
Do not do the obvious thing and dispose the HttpClient instance, even though the code "looks right":
async Task<HttpResponseMessage> Method() {
using (var client = new HttpClient())
return client.GetAsync(request);
}
Disposing the HttpClient instance can cause following HTTP requests started by other instances of HttpClient to be cancelled!
The same happens with C#'s new RIAA syntax; slightly less obvious:
async Task<HttpResponseMessage> Method() {
using var client = new HttpClient();
return client.GetAsync(request);
}
Instead, the correct approach is to cache a static instance of HttpClient for your app or library, and reuse it:
static HttpClient client = new HttpClient();
async Task<HttpResponseMessage> Method() {
return client.GetAsync(request);
}
(The Async() request methods are all thread safe.)
in my .net core 3.1 applications I am getting two problem where inner cause was timeout exception.
1, one is i am getting aggregate exception and in it's inner exception was timeout exception
2, other case was Task canceled exception
My solution is
catch (Exception ex)
{
if (ex.InnerException is TimeoutException)
{
ex = ex.InnerException;
}
else if (ex is TaskCanceledException)
{
if ((ex as TaskCanceledException).CancellationToken == null || (ex as TaskCanceledException).CancellationToken.IsCancellationRequested == false)
{
ex = new TimeoutException("Timeout occurred");
}
}
Logger.Fatal(string.Format("Exception at calling {0} :{1}", url, ex.Message), ex);
}
In my situation, the controller method was not made as async and the method called inside the controller method was async.
So I guess its important to use async/await all the way to top level to avoid issues like these.
I was using a simple call instead of async. As soon I added await and made method async it started working fine.
public async Task<T> ExecuteScalarAsync<T>(string query, object parameter = null, CommandType commandType = CommandType.Text) where T : IConvertible
{
using (IDbConnection db = new SqlConnection(_con))
{
return await db.ExecuteScalarAsync<T>(query, parameter, null, null, commandType);
}
}
Another reason can be that if you are running the service (API) and put a breakpoint in the service (and your code is stuck at some breakpoint (e.g Visual Studio solution is showing Debugging instead of Running)). and then hitting the API from the client code. So if the service code a paused on some breakpoint, you just hit F5 in VS.

What is the use of Cancellation token here

I've the following chunk of code :
using (var cancelSource = new CancellationTokenSource())
{
Task[] tasks = null;
var cancelToken = cancelSource.Token;
tasks = new[]
{
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000)) //<---
};
await Task.Delay(howLongSecs * 1000); // <---
cancelSource.Cancel();
await Task.WhenAll(tasks);
}
Where ThrowAfterAsync have this :
private async Task ThrowAfterAsync(string taskId, CancellationToken cancelToken, int afterMs)
{
await Task.Delay(afterMs, cancelToken);
var msg = $"{taskId} throwing after {afterMs}ms";
Console.WriteLine(msg);
throw new ApplicationException(msg);
}
Resharper is suggesting me that I can use the overload of Task.Run() with cancellation token like this :
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000), cancelToken)
But why ? What is the benefit of doing this over the first version, without the cancellation token as parameter ?
In this specific case, there is no point. In general, you'd want to do as it suggests because, by passing the token to Task.Run it can avoid even scheduling the operation in the first place if the token is cancelled before the operation even has a chance to start, but in your case you're creating the token and you know it won't be cancelled when you start the operation.
But the reason you don't need to pass the token to Task.Run is because the code starting that task is the operation responsible for cancelling the token, and so it knows that the token isn't cancelled yet. Normally you'd be accepting a token from elsewhere, and you wouldn't know if/when it was cancelled.
All that said, there's no reason to even use Task.Run at all. You can just write:
tasks = new[] { ThrowAfterAsync("C", cancelToken, 1000) };
It will have the same behavior but without needlessly starting a new thread just to start the asynchronous operation.
Next, your code will never return in less than howLongSecs seconds, even if the operation finishes before then, because of how you've structured your code. You should simply provide the timeout to the cancellation token source and let it take care of canceling the token at the right time, it won't delay the rest of your method if the operation finishes before the cancellation should happen, so your whole method can just be written as:
using (var cancelSource = new CancellationTokenSource(Timespan.FromSeconds(howLongSecs)))
{
await ThrowAfterAsync("C", cancelToken, 1000)
}
Resharper sees that you are using a method (Task.Run) which has overload which accepts CancellationToken, you have instance of CancellationToken in scope, but you do not use that overload which accepts a token. It does not perform any extensive analysys of your code - it's as simple as that. You can easily verify this with this code:
class Program {
static void Main() {
CancellationToken ct;
Test("msg"); // will suggest to pass CancellationToken here
}
private static void Test(string msg) {
}
private static void Test(string msg, CancellationToken ct) {
}
}
Yes the code itself is strange and you don't need to wrap your async in Task.Run at all, but I won't touch that since you asked just why Resharper suggests that.

How to async / await in Renci.SshNet.BeginDownload with Task.Factory.FromAsync

I have access to a Connected Renci.SshNet.SftpClient which I use to get a sequence of the files in the sftp folder. The function used for this is
Renci.SshNet.SftpClient.ListDirectory(string);
Due to the huge amount of files in the directory this takes about 7 seconds. I want to be able to keep my UI responsive using async / await and a cancellationToken.
If Renci.SshNet had a ListDirectoryAsync function that returned a Task, then this would be easy:
async Task<IEnumerable<SftpFiles> GetFiles(SftpClient connectedSftpClient, CancellationToken token)
{
var listTask connectedSftpClient.ListDirectoryAsync();
while (!token.IsCancellatinRequested && !listTask.IsCompleted)
{
listTask.Wait(TimeSpan.FromSeconds(0.2);
}
token.ThrowIfCancellationRequested();
return await listTask;
}
Alas the SftpClient doesn't have an async function. The following code works, however it doesn't cancel during the download:
public async Task<IEnumerable<SftpFile>> GetFilesAsync(string folderName, CancellationToken token)
{
token.ThrowIfCancellationRequested();
return await Task.Run(() => GetFiles(folderName), token);
}
However, the SftpClient does have kind of an async functionality using the functions
public IAsyncResult BeginListDirectory(string path, AsyncCallback asyncCallback, object state, Action<int> listCallback = null);
Public IEnumerable<SftpFile> EndListDirectory(IAsyncResult asyncResult);
In the article Turn IAsyncResult code into the new async and await Pattern is described how to convert an IAsyncResult into an await.
However I don't have a clue what to do with all the parameters in the BeginListdirectory and where to put the EndListDirectory. Anyone able to convert this into a Task on which I can wait with short timeouts to check the cancellation token?
It looks like the SftpClient does not follow the standard APM pattern: the listCallback is an extra parameter for the Begin method. As a result, I'm pretty sure you can't use the standard FromAsync factory method.
You can, however, write your own using TaskCompletionSource<T>. A bit awkward, but doable:
public static Task<IEnumerable<SftpFile>> ListDirectoryAsync(this SftpClient #this, string path)
{
var tcs = new TaskCompletionSource<IEnumerable<SftpFile>>();
#this.BeginListDirectory(path, asyncResult =>
{
try
{
tcs.TrySetResult(#this.EndListDirectory(asyncResult));
}
catch (OperationCanceledException)
{
tcs.TrySetCanceled();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}, null);
return tcs.Task;
}
(code written in browser and completely untested :)
I structured it as an extension method, which is the approach I prefer. This way your consuming code can do a very natural connectedSftpClient.ListDirectoryAsync(path) kind of call.

Task ContinueWith does not return expected value

This is probably one of those RTFM type questions, however, I cannot for the life of me figure out how chain tasks together in a way that works for Asp.net WebApi.
Specifically, I am looking how to use a DelegatingHandler to modify the response after it has been handled by the controller (add an additional header), and I am trying to unit test the DelegatingHandler by using a HttpMessageInvoker instance.
First Pattern
[TestMethod]
public void FirstTest()
{
var task = new Task<string>(() => "Foo");
task.ContinueWith(t => "Bar");
task.Start();
Assert.AreEqual("Bar", task.Result);
}
This fails on the assert because task.Result returns "Foo"
Second Pattern
[TestMethod]
public void SecondTest()
{
var task = new Task<string>(() => "Foo");
var continueTask = task.ContinueWith(t => "Bar");
continueTask.Start();
Assert.AreEqual("Bar", continueTask.Result);
}
This fails on continueTask.Start() with the exception of System.InvalidOperationException: Start may not be called on a continuation task.
Third Pattern
[TestMethod]
public void ThirdTest()
{
var task = new Task<string>(() => "Foo");
var continueTask = task.ContinueWith(t => "Bar");
task.Start();
Assert.AreEqual("Bar", continueTask.Result);
}
This test works the way I expect, however, I am not sure how to get this pattern to work with WebAPI.
Current Implementation
public class BasicAuthenticationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var task = base.SendAsync(request, cancellationToken);
task.ContinueWith(AddWwwAuthenticateHeaderTask());
return task;
}
private static Func<Task<HttpResponseMessage>, HttpResponseMessage>
AddWwwAuthenticateHeaderTask()
{
return task =>
{
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(
new AuthenticationHeaderValue("Basic", "realm=\"api\""));
}
return response;
};
}
}
However, when I invoke BasicAuthenticationHandler from a unit test, my header is not added before the Assert happens (if I debug, I notice that the header is added after the unit test fails).
[TestMethod]
public void should_give_WWWAuthenticate_header_if_authentication_is_missing()
{
using (var sut = new BasicAuthenticationHandler())
{
sut.InnerHandler = new DelegatingHttpMessageHandler(
() => new HttpResponseMessage(HttpStatusCode.Unauthorized));
using (var invoker = new HttpMessageInvoker(sut))
{
var task = invoker.SendAsync(_requestMessage, CancellationToken.None);
task.Start();
Assert.IsTrue(
task.Result.Headers.WwwAuthenticate.Contains(
new AuthenticationHeaderValue("Basic", "realm=\"api\"")));
}
}
}
If I change my production code to return the continuation task instead of the result from base.SendAsync then I get the 2nd unit test exception about calling Start on a continuation task.
I think I want to accomplish the third unit test pattern in my production code, however, I have no idea on how to write that.
How do I do what I want (add the header before the assert gets called)?
When returning a Task, it should always be a Hot Task, meaning that the returned task has already been started. Making someone explicitly call Start() on a returned task is confusing and violates the guidelines.
To properly see the continuations result, do this:
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(AddWwwAuthenticateHeaderTask()).Unwrap();
}
And you should modify base.SendAsync to return the already started Task
From the Task Asynchronous Pattern Guidelines:
All tasks returned from TAP methods must be “hot.” If a TAP method internally uses a Task’s constructor to instantiate the task to be returned, the TAP method must call Start on the Task object prior to returning it. Consumers of a TAP method may safely assume that the returned task is “hot,” and should not attempt to call Start on any Task returned from a TAP method. Calling Start on a “hot” task will result in an InvalidOperationException (this check is handled automatically by the Task class).
I cannot for the life of me figure out how chain tasks together in a way that works for Asp.net WebApi.
Embrace async and await. In particular, replace ContinueWith with await (and do not use the task constructor or Start):
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(
new AuthenticationHeaderValue("Basic", "realm=\"api\""));
}
return response;
}
Try the following. Note task2.Unwrap(), I think this part hasn't been addressed by the other answers:
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var task1 = base.SendAsync(request, cancellationToken);
var task2 = task1.ContinueWith(t => AddWwwAuthenticateHeaderTask(),
cancellationToken);
return task2.Unwrap();
}
You need to unwrap the inner task because the type of task2 is Task<Task<HttpResponseMessage>>. This should provide correct continuation semantic and result propagation.
Check Stephen Toub's "Processing Sequences of Asynchronous Operations with Tasks". This complexity can be avoided with async/await.
If you can't use async/await, you still can further improve this code a bit, to avoid redundant thread switching otherwise caused by ContinueWith:
var task2 = task1.ContinueWith(
t => AddWwwAuthenticateHeaderTask(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
In either case, the continuation will not happen on the original synchronization context (which probably is AspNetSynhronizationContext). If you need to stay on the same context, use TaskScheduler.FromCurrentSynchronizationContext() instead of TaskScheduler.Default. A word of caution: this may cause a deadlock in ASP.NET.
ContinueWith returns the mapped task so you need to return that:
var task = base.SendAsync(request, cancellationToken);
return task.ContinueWith(AddWwwAuthenticateHeaderTask());

Distinguish timeout from user cancellation

HttpClient has a builtin timeout feature (despite being all asynchronous, i.e. timeouts could be considered orthogonal to the http request functionality and thus be handled by generic asynchronous utilities, but that aside) and when the timeout kicks in, it'll throw a TaskCanceledException (wrapped in an AggregateException).
The TCE contains a CancellationToken that equals CancellationToken.None.
Now if I provide HttpClient with a CancellationToken of my own and use that to cancel the operation before it finishes (or times out), I get the exact same TaskCanceledException, again with a CancellationToken.None.
Is there still a way, by looking only at the exception thrown, to figure out whether a timeout canceled the request, without having to make my own CancellationToken accessible to the code that checks the exception?
P.S. Could this be a bug and CancellationToken got somehow wrongly fixed to CancellationToken.None? In the cancelled using custom CancellationToken case, I'd expect TaskCanceledException.CancellationToken to equal that custom token.
Edit
To make the problem a bit more clear, with access to the original CancellationTokenSource, it is easy to distinguish timeout and user cancellation:
origCancellationTokenSource.IsCancellationRequested == true
Getting the CancellationToken from the exception though gives the wrong answer:
((TaskCanceledException) e.InnerException).CancellationToken.IsCancellationRequested == false
Here a minimal example, due to popular demand:
public void foo()
{
makeRequest().ContinueWith(task =>
{
try
{
var result = task.Result;
// do something with the result;
}
catch (Exception e)
{
TaskCanceledException innerException = e.InnerException as TaskCanceledException;
bool timedOut = innerException != null && innerException.CancellationToken.IsCancellationRequested == false;
// Unfortunately, the above .IsCancellationRequested
// is always false, no matter if the request was
// cancelled using CancellationTaskSource.Cancel()
// or if it timed out
}
});
}
public Task<HttpResponseMessage> makeRequest()
{
var cts = new CancellationTokenSource();
HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) };
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "url");
passCancellationTokenToOtherPartOfTheCode(cts);
return client.SendAsync(httpRequestMessage, cts.Token);
}
The accepted answer is certainly how this should work in theory, but unfortunately in practice IsCancellationRequested does not (reliably) get set on the token that is attached to the exception:
Cancelling an HttpClient Request - Why is TaskCanceledException.CancellationToken.IsCancellationRequested false?
Yes, they both return the same exception (possibly because of timeout internally using a token too) but it can be easily figured out by doing this:
catch (OperationCanceledException ex)
{
if (token.IsCancellationRequested)
{
return -1;
}
return -2;
}
so basically if you hit the exception but your token is not cancelled, well it was a regular http timeout

Categories

Resources