Reputation: 181
I have a wrapper that creates a function public async Task getCacheToken created for a few internal services/Application to call
and I'm experiencing this exception (please see the following) by calling performExtract() in another service.
performExtract is literally calling getCacheToken via API call
I can't help to send an async call inside the sync method (legacy environment), so every time when I call var results = client.SendAsync(requestData).Result' in a loop it will cause deadlocks, if I understand it correctly, sendAsync inside for loop, it will wait for a task to be finished before starting another one, so it should not have an exception of the following (connection disposed?)
to fix it, I have to override send Async ConfigureAwait(false) and it resolved my problem.
my question is how adding ConfigureAwait(false) solve the problem?
To avoid this issue, you can use a method called ConfigureAwait with a false parameter. When you do, this tells the Task that it can resume itself on any thread that is available instead of waiting for the thread that originally created it. This will speed up responses and avoid many deadlocks.
and how running it async cause a deadlock?
Thanks so much for all your patient reading thru the post.
protected override ExtractResultStatus PerformExtract()
{
//EngageRestClient client = new EngageRestClient(_apiEndPoint);
//client.Authenticator = new NtlmAuthenticator();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
try
{
var numErrors = 0;
var dt = GetInfos();
var fileName = string.Format(FilenameBase, DateTime.Now);
if (dt.Rows.Count > 0)
{
dt.Columns.Add("failureReason");
foreach (DataRow row in dt.Rows)
{
var referenceID = row["U3l_ReferenceId"].ToString();
var requestData = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(_apiEndPoint + $"?referenceID={referenceID}"),
};
requestData.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
var results = client.SendAsync(requestData).Result;
var resultResponse = results.Content.ReadAsStringAsync().Result;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
for (int i = 0; i < MaxRetries; i++)
{
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode != HttpStatusCode.InternalServerError ||
response.StatusCode != HttpStatusCode.NotImplemented ||
response.StatusCode != HttpStatusCode.GatewayTimeout ||
response.StatusCode != HttpStatusCode.ServiceUnavailable)
{
return response;
}
response.Dispose();
}
return response;
}
public async Task<string> GetCacheToken()
{
ObjectCache cache = MemoryCache.Default;
string refreshToken = cache.Get("refreshToken", null) == null ? GetToken() : cache.Get("refreshToken", null).ToString();
if (!cache.Contains("apiToken"))
{
var httpContent = new StringContent("", Encoding.UTF8, "application/x-www-form-urlencoded");
var dict = new Dictionary<string, string>();
dict.Add("grant_type", "refresh_token");
dict.Add("refresh_token", refreshToken);
var requestData = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("https://oauth2.sky.blackbaud.com/token"),
Content = new FormUrlEncodedContent(dict)
};
requestData.Headers.Authorization = new AuthenticationHeaderValue("Basic", Settings.BasicAuth);
var results = await _client.SendAsync(requestData);
var resultResponse = results.Content.ReadAsStringAsync().Result;
try
{
results.EnsureSuccessStatusCode();
var result = _js.Deserialize<TokenModel>(resultResponse);
//token expires in one hour from blackbaud
var expiration = DateTimeOffset.UtcNow.AddMinutes(55);
cache.Add("apiToken", result.access_token, expiration);
cache.Add("refreshToken", result.refresh_token, expiration);
UpdateToken(result.access_token, result.refresh_token);
}
catch (Exception e)
{
var exceptionMessage = $"ResultMessage : {resultResponse} Exception: {e}. Message: {e.Message}. Stacktrace {e.StackTrace}";
Log.Exception(e,exceptionMessage);
throw;
}
}
return cache.Get("apiToken", null).ToString();
}
{Data: [], HResult: -2146233088, HelpLink: null, InnerException: null, Message: "Response status code does not indicate success: 400 (Bad Request).", Source: "System.Net.Http", StackTrace: " at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\r\n at RaisersEdge.Infrastructure.Cache.d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at RaisersEdge.Controllers.BaseController.d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Threading.Tasks.TaskHelpersExtensions.d__3`1.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ApiControllerActionInvoker.d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()", TargetSite: "System.Net.Http.HttpResponseMessage EnsureSuccessStatusCode()", _typeTag: "HttpRequestException"}
Upvotes: 0
Views: 2860
Reputation: 456777
how running it async cause a deadlock?
await
by default captures a context and resumes executing the async
method in that context. If this context only allows one thread at a time, and the calling code blocks a thread in that context by calling Result
or Wait
, then the code causes a deadlock since the async
method cannot resume (and thus cannot complete).
how adding ConfigureAwait(false) solve the problem?
Because the await
no longer captures its context. The async
method can resume on any thread pool thread, and isn't affected by the thread blocked in the context. ConfigureAwait(false)
is generally considered a best practice for library code.
one of the service is in a legacy environment cannot call await for because I don't have an async function to override with
There are a variety of hacks you can use to attempt to block on asynchronous code safely. None of them work in every situation. If ConfigureAwait(false)
works for you, then I would use that.
Upvotes: 2
Reputation: 6073
As other folks mentioned you SHOULD not use .Result
because it's evil! but in your case that you are working with a legacy app you can use this workaround:
using System.Threading.Tasks;
public class AsyncHelper
{
private static readonly TaskFactory _taskFactory = new
TaskFactory(CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
public static TReturn RunSync<TReturn>(Func<Task<TReturn>> task)
{
return _taskFactory.StartNew(task)
.Unwrap()
.GetAwaiter()
.GetResult();
}
}
Then you can easily call your method with the helper:
var results = AsyncHelper.RunSync<System.Net.Http.HttpResponseMessage>(
() => client.SendAsync(requestData)
);
The helper class creates, configure and runs an async task, then unwraps it and waits for it synchronously to get the results: This is almost what await does, this approach will prevent deadlocks and could be used within a try/catch block.
Of course, the only correct way to calling async
method is using await
but this workaround is a better option when you have no way to call an async
method inside sync method.
Upvotes: 3