Converting async/await to Task.ContinueWith - c#

This question was triggered by comments to this one:
How to back-port a non-linear async/await code to .NET 4.0 without Microsoft.Bcl.Async?
In the linked question, we have a WebRequest operation we want to retry for a limited number of times, if it keeps failing. The Async/await code could look like this:
async Task<HttpWebResponse> GetResponseWithRetryAsync(string url, int retries)
{
if (retries < 0)
throw new ArgumentOutOfRangeException();
var request = WebRequest.Create(url);
while (true)
{
WebResponse task = null;
try
{
task = request.GetResponseAsync();
return (HttpWebResponse)await task;
}
catch (Exception ex)
{
if (task.IsCanceled)
throw;
if (--retries == 0)
throw; // rethrow last error
// otherwise, log the error and retry
Debug.Print("Retrying after error: " + ex.Message);
}
}
}
From the first thought, I'd use TaskCompletionSource, as something like this (untested):
Task<HttpWebResponse> GetResponseWithRetryAsync(string url, int retries)
{
if (retries < 0)
throw new ArgumentOutOfRangeException();
var request = WebRequest.Create(url);
var tcs = new TaskCompletionSource<HttpWebResponse>();
Action<Task<WebResponse>> proceesToNextStep = null;
Action doStep = () =>
request.GetResponseAsync().ContinueWith(proceedToNextStep);
proceedToNextStep = (prevTask) =>
{
if (prevTask.IsCanceled)
tcs.SetCanceled();
else if (!prevTask.IsFaulted)
tcs.SetResult((HttpWebResponse)prevTask.Result);
else if (--retries == 0)
tcs.SetException(prevTask.Exception);
else
doStep();
};
doStep();
return tcs.Task;
}
The question is, how to do this without TaskCompletionSource?

I've figured out how to do it without async/await or TaskCompletionSource, using nested tasks and Task.Unwrap instead.
First, to address #mikez's comment, here's GetResponseAsync implementation for .NET 4.0:
static public Task<WebResponse> GetResponseTapAsync(this WebRequest request)
{
return Task.Factory.FromAsync(
(asyncCallback, state) =>
request.BeginGetResponse(asyncCallback, state),
(asyncResult) =>
request.EndGetResponse(asyncResult), null);
}
Now, here's GetResponseWithRetryAsync implementation:
static Task<HttpWebResponse> GetResponseWithRetryAsync(string url, int retries)
{
if (retries < 0)
throw new ArgumentOutOfRangeException();
var request = WebRequest.Create(url);
Func<Task<WebResponse>, Task<HttpWebResponse>> proceedToNextStep = null;
Func<Task<HttpWebResponse>> doStep = () =>
{
return request.GetResponseTapAsync().ContinueWith(proceedToNextStep).Unwrap();
};
proceedToNextStep = (prevTask) =>
{
if (prevTask.IsCanceled)
throw new TaskCanceledException();
if (prevTask.IsFaulted && --retries > 0)
return doStep();
// throw if failed or return the result
return Task.FromResult((HttpWebResponse)prevTask.Result);
};
return doStep();
}
It's been an interesting exercise. It works, but I think its the way more difficult to follow, than the async/await version.

Related

Return the results of Tasks - including the exceptions?

The following code run M1, M2, M3, M4 in parallel. Each method may raise exceptions. The method should return the results of the four async methods - either the int returned by methods or the Exceptions.
async Task<string> RunAll()
{
int m1result, m2result, m3result, m4result;
try
{
var m1task = M1();
var m2task = M2();
var m3task = M3();
var m4task = M4();
// await Task.WhenAll(new Task<int>[] { m1task, m2task, m3task, m4task });
m1result = await m1task;
m2result = await m2task;
m3result = await m3task;
m4result = await m4task;
}
catch (Exception ex)
{
// need to return the ex of the failed task. How?
}
// How to implement M1HasException, M2HasException, ... in the following lines?
var m1msg = M1HasException ? M1ExceptionMessage : m1result.ToString();
var m2msg = M2HasException ? M2ExceptionMessage : m2result.ToString();
var m3msg = M3HasException ? M3ExceptionMessage : m3result.ToString();
var m4msg = M4HasException ? M4ExceptionMessage : m4result.ToString();
return $"M1: {m1msg}, M2: {m2msg}, M3: {m3msg}, M4: {m4msg}";
}
How to capture the individual exceptions of the failed task?
For example, if only M2 threw an exception,
"M1: 1, M2: Excpetion...., M3: 3, M4: 4"
Each task has a Status and and Exception property.
You may want to see if it has faulted:
myTask.Status == TaskStatus.Faulted
Or if it has excepted:
if (myTask.Exception != null)
You can use ContinueWhenAll to run all the tasks and then check the status.
See the docs here.
As other answers/comments pointed out, one possible approach is by using ContinueWith or ContinueWhenAll. This is a clever trick because Task has the Exception property:
Gets the AggregateException that caused the Task to end prematurely.
If the Task completed successfully or has not yet thrown any
exceptions, this will return null.
Using ContinueWith whether a task completes successfully or not, it will be passed as the argument to the delegate function. From there you can check if an exception was thrown.
Task<string> GetStringedResult<T>(Task<T> initialTask)
{
return initialTask.ContinueWith(t => {
return t.Exception?.InnerException.Message ?? t.Result.ToString();
});
}
async Task<string> RunAll()
{
string m1result, m2result, m3result, m4result;
var m1task = GetStringedResult(M1());
var m2task = GetStringedResult(M2());
var m3task = GetStringedResult(M3());
var m4task = GetStringedResult(M4());
m1result = await m1task;
m2result = await m2task;
m3result = await m3task;
m4result = await m4task;
return $"M1: {m1result}, M2: {m2result}, M3: {m3result}, M4: {m4result}";
}
You can wrap the tasks inside a WaitAll and catch the AggregateException (docs),
try
{
Task.WaitAll(new[] { task1, task2 }, token);
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
//Do what ever you want with the ex.
}
Could you wrap each await in try-catch block and capture the exception message if any, seems feasible...
var results = new List<string>();
try { results.Add(await t1); } catch { results.Add("Exception"); };
try { results.Add(await t2); } catch { results.Add("Exception"); };
try { results.Add(await t3); } catch { results.Add("Exception"); };
return string.Join("|", results);
if you want to use WhenAll you could await for it and ignore exceptions and then do the same exercise as shown above to retrieve individual task results...
try { await Task.WhenAll(t1, t2, t3); } catch { };
// ^^^^^^^^^
// then same as ^ above

async processing in c#

I have a service that implements functionality to return data from sentiment analysis APIs. The client can request results from one or all engines and I want to collate all the data together. I want to process these async and wait for them all to complete before returning the result set. I'm new to async programming and I really cant figure out how to arrange the code and how to implement it syntactically. Here's an EXAMPLE of what I'm TRYING to achieve (I know this doesn't work, but you get the idea; hopefully :-) ):
private ISentimentResponse ProcessRequest(ISentimentRequest request, SentimentEngineServices selectedEngines)
{
SentimentResponse response = new SentimentResponse();
List<Task> taskList = new List<Task>();
foreach (SentimentEngineServices engineService in (SentimentEngineServices[])Enum.GetValues(typeof(SentimentEngineServices)))
{
if (((int)engineService & (int)selectedEngines) > 0)
{
ISentimentEngine engine = _engineFactory.GetSentimentEngine(engineService, null);
Task<ISentimentEngineResult> task = new Task<ISentimentEngineResult>(engine.AnalyseSentimentASync(request));
taskList.Add(task);
}
}
if (taskList.Count > 0)
{
ISentimentEngineResult[] results = Task<ISentimentEngineResult>.WaitAll(taskList);
foreach (result in results)
response.Add(results);
}
return response;
}
The engine has the following code implementation of engine.AnalyseSentimentASync:
public ISentimentEngineResult AnalyseSentiment(ISentimentRequest request)
{
try
{
MultiLanguageBatchInput sentimentList = SentimentRequestToMicrosoftBatchInput(request, Properties.Settings.Default.DefaultLanguage);
SentimentBatchResult sentiment = _client.Sentiment(sentimentList);
KeyPhraseBatchResult keyPhrases = _client.KeyPhrases(sentimentList);
return MicrosoftBatchResultsToSentimentEngineResult(sentiment, keyPhrases);
}
catch (Exception ex)
{
_logger.LogMessage(ex,$"{EngineName} threw an unknown exception: ", LoggingLevel.Error);
throw;
}
}
public async Task<ISentimentEngineResult> AnalyseSentimentAsync(ISentimentRequest request)
{
return AnalyseSentiment(request);
}
What do I need to do and is there any better way to achieve this?
I've looked everywhere for an example but I cant find one that addresses my implementation requirements, or the whole approach is completely wrong!
Thanks all,
Stu.
This is how you can convert it to async:
public async Task<ISentimentEngineResult> AnalyseSentimentAsync(ISentimentRequest request)
{
try
{
MultiLanguageBatchInput sentimentList = SentimentRequestToMicrosoftBatchInput(request, Properties.Settings.Default.DefaultLanguage);
SentimentBatchResult sentiment = await _client.SentimentAsync(sentimentList);
KeyPhraseBatchResult keyPhrases = await _client.KeyPhrasesAsync(sentimentList);
return MicrosoftBatchResultsToSentimentEngineResult(sentiment, keyPhrases);
}
catch (Exception ex)
{
_logger.LogMessage(ex,$"{EngineName} threw an unknown exception: ", LoggingLevel.Error);
throw;
}
}
private async Task<ISentimentResponse> ProcessRequestAsync(ISentimentRequest request, SentimentEngineServices selectedEngines)
{
SentimentResponse response = new SentimentResponse();
List<Task<ISentimentEngineResult>> taskList = new List<Task<ISentimentEngineResult>>();
foreach (SentimentEngineServices engineService in (SentimentEngineServices[])Enum.GetValues(typeof(SentimentEngineServices)))
{
if (((int)engineService & (int)selectedEngines) > 0)
{
ISentimentEngine engine = _engineFactory.GetSentimentEngine(engineService, null);
Task<ISentimentEngineResult> task = engine.AnalyseSentimentASync(request);
taskList.Add(task);
}
}
if (taskList.Count > 0)
{
ISentimentEngineResult[] results = await Task.WhenAll(taskList);
foreach (result in results)
response.Add(results);
}
return response;
}
Remember that you have to call it from some kind of event handler. I don't know what framework you are using (wpf, asp.net, windows service, webapi).
Ok, so here it is:
public ISentimentResponse AnalyseSentiment(ISentimentRequest request, SentimentEngineServices selectedEngines)
{
if (selectedEngines == SentimentEngineServices.None) throw new ArgumentException(nameof(selectedEngines));
ValidateRequest(request);
return ProcessRequestAsync(request, selectedEngines).Result;
}
private async Task<ISentimentResponse> ProcessRequestAsync(ISentimentRequest request, SentimentEngineServices selectedEngines)
{
SentimentResponse response = new SentimentResponse();
List<Task<ISentimentEngineResult>> taskList = new List<Task<ISentimentEngineResult>>();
foreach (SentimentEngineServices engineService in (SentimentEngineServices[])Enum.GetValues(typeof(SentimentEngineServices)))
{
if (((int)engineService & (int)selectedEngines) > 0)
{
ISentimentEngine engine = _engineFactory.GetSentimentEngine(engineService, null);
Task<ISentimentEngineResult> task = engine.AnalyseSentimentASync(request);
taskList.Add(task);
}
}
if (taskList.Count > 0)
{
ISentimentEngineResult[] results = await Task.WhenAll(taskList);
foreach (var result in results)
response.Add(result);
}
return response;
}
And the sentiment interface implementation:
public async Task<ISentimentEngineResult> AnalyseSentiment(ISentimentRequest request)
{
try
{
MultiLanguageBatchInput sentimentList = SentimentRequestToMicrosoftBatchInput(request, Properties.Settings.Default.DefaultLanguage);
SentimentBatchResult sentiment = await _client.SentimentAsync(sentimentList);
KeyPhraseBatchResult keyPhrases = await _client.KeyPhrasesAsync(sentimentList);
return MicrosoftBatchResultsToSentimentEngineResult(sentiment, keyPhrases);
}
catch (Exception ex)
{
_logger.LogMessage(ex,$"{EngineName} threw an unknown exception: ", LoggingLevel.Error);
throw;
}
}
Thanks FCin.

(Async & await) vs (without Async & await) in Web API

I am new with Async and await using C# Programming. In WebAPI, we have created two API Controllers one with Async and await Programming and other is without that. We have done load testing using JMeter and we have got following results.
Users Sync Async
100 No Errors No Errors
500 No Errors No Errors
750 No Errors Errors - (59.0 %) - 502 Bad Gateway
763 No Errors Errors
764 No Errors Errors
765 Errors - (0.13 %) - 502 Bad Gateway Errors
1000 Errors Errors
Can you any please explain/suggest which approach is best or how can we proceed ?
API Code :
GetPersonalDetailsController - Async and await Used
public async Task<IHttpActionResult> GET([FromUri] RequestQueryListDTO objAPIRequest)
{
DateTime startResponseTime = DateTime.Now;
Response objResponse = null;
string strResponse = string.Empty;
var HeaderType = Request.Content.Headers.ContentType;
ProductBAL objProductBAL = null;
try
{
if (objAPIRequest != null)
{
Task<Response> tskGetProductDetails = Task<Response>.Run(() =>
{
objProductBAL = new ProductBAL();
return objProductBAL.GetProductDetails(objAPIRequest);
//Business Access Layer Logic calling
});
objResponse = await tskGetProductDetails;
}
else
{
objResponse = new Response();
objResponse.ReturnCode = -1;
objResponse.ReturnMessage = "Missing Parameters.";
}
}
catch (Exception ex)
{
\\ Exception Logging
}
finally
{
objProductBAL = null;
}
objResponse.ResponseTime = Math.Round((DateTime.Now - startResponseTime).TotalMilliseconds).ToString();
if (objResponse.ReturnCode == Convert.ToInt32(General.ReturnCode))
{
return Content<Response>(HttpStatusCode.BadRequest, objResponse);
}
else
{
return Ok(objResponse);
}
}
========================================================================
GetPDPController - Without using Async and await
public IHttpActionResult GET([FromUri] RequestQueryListDTO objAPIRequest)
{
DateTime startResponseTime = DateTime.Now;
Response objResponse = null;
string strResponse = string.Empty;
var HeaderType = Request.Content.Headers.ContentType;
try
{
if (objAPIRequest != null)
{
//Business Access Layer Logic calling
}
else
{
objResponse = new Response();
objResponse.ReturnCode = -1;
objResponse.ReturnMessage = "Missing Parameters.";
}
}
catch (Exception ex)
{
// Exception Logging Code
}
finally
{
objProductBAL = null;
}
objResponse.ResponseTime = Math.Round((DateTime.Now - startResponseTime).TotalMilliseconds).ToString();
if (objResponse.ReturnCode == Convert.ToInt32(General.ReturnCode))
{
return Content<Response>(HttpStatusCode.BadRequest, objResponse);
}
else
{
return Ok(objResponse);
}
}
My suggestion is have two methods, one Async and one not. That way you can test more.
GetProductDetails
GetProductDetailsAsync
you would then need to change the signature of the calling method aka GET
public IHttpActionResult GET([FromUri] RequestQueryListDTO objAPIRequest)
{
var objResponse = new Response();
//check the properties of objAPIRequest
if(bad)
{
//add stuff if you want
return Content<Response>(HttpStatusCode.BadRequest, objResponse);
}
//Business Access Layer Logic calling
//-----------------------
ProductBAL objProductBAL = new ProductBAL();
//you need to change this to async
var productDetails = objProductBAL.GetProductDetails(objAPIRequest);
//-----------------------
return Ok(objResponse);
}

Exception: Microsoft.Azure.Documents.RequestRateTooLargeException while querying from DocumentDB

y query is something like
this.ProcessRequestSync(() => this.Client.CreateDocumentQuery<Model>(this.DocumentDBCollectionLink).Where(d => d.name.Equals(name) && d.code.Equals(code) && d.Type.Equals(this.documentType) && d.CreatedBy.Equals(myName).ToList<Model>());
public dynamic ProcessRequestSync(Func<dynamic> getRequest)
{
var delay = TimeSpan.Zero;
var minDelayTime = new TimeSpan(0, 0, 1);
for (;;)
{
try
{
Thread.Sleep(delay);
return getRequest();
}
catch (DocumentClientException documentClientException)
{
var statusCode = (int)documentClientException.StatusCode;
if (statusCode == 429 || statusCode == 503)
{
string errorMessage = string.Format("failed at DocumentDB with {0} status and {1} retry time", statusCode, documentClientException.RetryAfter);
this.Logger.Log(errorMessage );
// Back off if the request rate is too large or the service is temporarily unavailable
delay = TimeSpan.Compare(documentClientException.RetryAfter, minDelayTime) >= 0 ? documentClientException.RetryAfter: minDelayTime;
}
else
{
throw;
}
}
}
}
This is the method for retry logic when requestRateTooLarge exception raise.
I am not sure, whether it is working fine or not,
I am getting Exception: Microsoft.Azure.Documents.RequestRateTooLargeException while querying and inserting around 4000 records at a time,
I used the same retry logic for inserting, its working fine.
I am not getting any error and also successfully inserted all records but unable to get query data.
Based on #aravind Ramachandra and #Ryan CrawCour answers above/below, this is what I am using to get round the issue.
public async Task SaveToDocDb(dynamic jsonDocToSave)
{
using (var client = new DocumentClient(endpoint, authKey))
{
var queryDone = false;
while (!queryDone)
{
try
{
await client.CreateDocumentAsync(docCollectionlink, jsonDocToSave);
queryDone = true;
}
catch (DocumentClientException documentClientException)
{
var statusCode = (int)documentClientException.StatusCode;
if (statusCode == 429 || statusCode == 503)
Thread.Sleep(documentClientException.RetryAfter);
else
throw;
}
catch (AggregateException aggregateException)
{
if(aggregateException.InnerException.GetType() == typeof(DocumentClientException)){
var docExcep = aggregateException.InnerException as DocumentClientException;
var statusCode = (int)docExcep.StatusCode;
if (statusCode == 429 || statusCode == 503)
Thread.Sleep(docExcep.RetryAfter);
else
throw;
}
else
throw;
}
}
}
}
You also need a catch block for AggregateException, and check if the AggregateException.InnerException is a DocumentClientException and performs the same check for StatusCode == 429. Since the query execution is asynchronous, you might be getting the throttle exception wrapped inside an AggregateException.
If you could post a full repro, we might able to definitively identify the problem.

One or more errors occurred: PostAsJsonAsync

This is the error I get:
One or more errors occurred.
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
at System.Threading.Tasks.Task`1.get_Result()
at myProject.mymethod(Int32[] myIds) in path\MyClass.cs:line 758
And here's that method:
private void mymethod(int[] myIds)
{
var uri = new Uri(string.Format(UriPath, string.Format(MyPath)));
var client = GetHttpClient(uri);
var postModel = new PostModel
{
Ids = myIds,
LastUpdate = NewItem ? null : _lastUpdated
};
if (client != null)
{
var response = client.PostAsJsonAsync(uri, postModel).Result;//this line crashes
if (response.IsSuccessStatusCode)
{
//doSomething
}
}
}
I call a lot of methods like this and all of them work except this one. When it's hit, it takes a lot of time and then this exception is throws. With all of the other methods the error doesn't happen.
This is the inner exception:
Inner.System.Threading.Tasks.TaskCanceledException: A task was canceled.
Here's my GetCLient() method:
private HttpClient GetHttpClient(Uri uri)
{
var handler = new HttpClientHandler
{
CookieContainer = CoockieContainer
};
return new HttpClient(handler)
{
BaseAddress = uri
};
}
Here's the API method:
[HttpPost]
public IList<MyModel> MyAPIMethod(PostModel model)
{
List<MyModel> myTranslations;
using (var db = new myEntities(GetDbConnStrByUser(new GetCookies().GetUserName())))
{
myTranslations = db.tblTranslations.Where(it => model.Ids.Contains(it.id)
&& (!model.Update.HasValue || it.update > model.LastUpdate.Value))
.Select(it => new MyModel
{
Id = it.id,
Name = it.name,
Description = it.desc,
LanguageId = it.language_id
}).ToList();
}
return myTranslations.GroupBy(x => new { x.Id, x.LanguageId }).Select(x => x.First()).ToList();
}
Maybe a timeout occurs.
Fiddler returns this error: The wait operation timed out.
.Result tries to turn Task into T. It synchronously blocks waiting for the task to complete and will only return T if no exceptions occur. The two types of exceptions you should anticipate are OperationCanceledExceptions/TaskCanceledExceptions (when the HTTP request times out or a CancellationToken was used and the token was cancelled--I don't think the latter is applicable here) and general/HTTP-related exceptions.
You're probably running into the HTTP timeout scenario. Is it taking a long time for the exception to get thrown? If not, what does your GetHttpClient method look like? Is it explicitly setting timeouts or cancellation tokens?
If you want to stick with the synchronous approach and not returns Tasks, you should do something like this instead:
try
{
var response = client.PostAsJsonAsync(uri, postModel).Result;//this line crashes
}
catch (OperationCanceledException oce)
{
// Tell the user that the request timed our or you cancelled a CancellationToken
}
catch (Exception exc)
{
// Look at the HttpClient docs and try to catch something more specific. You don't want
// to swallow StackOverflowExceptions or OutOfMemoryExceptions.
}
If you're willing to take the async plunge, this is more what you're looking for:
private async Task mymethodAsync(int[] myIds)
{
var uri = new Uri(string.Format(UriPath, string.Format(MyPath)));
var client = GetHttpClient(uri);
var postModel = new PostModel { ... };
if (client != null)
{
try
{
var response = await client.PostAsJsonAsync(uri, postModel);
if (response.IsSuccessStatusCode)
{
//doSomething
}
}
catch (OperationCanceledException oce)
{
// because timeouts are still possible
}
catch (Exception exc) {
// Because other things can still go wrong too (HTTP 500, parsing errors)
}
}
}
See here for more info on how .Result call works w/HttpClient: What happens while waiting on a Task's Result?

Categories