Blazor Server Initiating Task Halts Application - c#

I have a Blazor Server app that calls a number of APIs. Everything works fine, but I am trying to wrap these calls in Tasks. As soon as my code gets to the call, everything just stops. I am sure I am doing something stupid, but no end of Googling is finding me the solution. The call comes from a Syncfusion Grid when selecting a row. Here is my minimum reproducable code:
public static IEnumerable<Quotation> customerQuotations = Array.Empty<Quotation>();
public async Task CustomerRowSelectHandler(RowSelectEventArgs<Customer> args)
{
GetCustomerQuotes(args.Data.customerId);
}
static async void GetCustomerQuotes(int customerId)
{
string url = string.Format(#"https://my.server.dns/quotations/customer/{0}", customerId);
var task = GetJsonString(url);
task.Wait();
customerQuotations = (IEnumerable<Quotation>)JsonConvert.DeserializeObject<Quotation>(task.Result);
}
private static async Task<string> GetJsonString(string url)
{
var TCS = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
HttpResponseMessage response = await myClient.GetAsync(url);
string streamResponse = await response.Content.ReadAsStringAsync();
TCS.SetResult(streamResponse);
return await TCS.Task;
}
If I do this how I am doing all of my other calls, i.e. not using a Task, it works fine, so I know it's not a silly error, it's something I am missing in my Task call.
Thanks in anticipation of any help offered.

The main problem, is Task.Wait(). That can deadlock.
public async Task CustomerRowSelectHandler(RowSelectEventArgs<Customer> args)
{
//GetCustomerQuotes(args.Data.customerId);
await GetCustomerQuotes(args.Data.customerId);
}
//static async void GetCustomerQuotes(int customerId)
async Task GetCustomerQuotes(int customerId)
{
string url = string.Format(#"https://my.server.dns/quotations/customer/{0}", customerId);
var task = GetJsonString(url);
// task.Wait();
await task;
customerQuotations = (IEnumerable<Quotation>)JsonConvert.DeserializeObject<Quotation>(task.Result);
}
and of course
var task = GetJsonString(url);
await task;
... (task.Result)
can (should) become
string result = await GetJsonString(url);
... (result)
And when you don't need the response object (for status code etc) then all this can be done in 1 line:
customerQuotations = await myClient.GetFromJsonAsync<Quotation[]>(url);

It looks like you are overcomplicating the async coding in the API call. Why do you need to construct a TaskCompletionSource? You may have reasons, but they are not evident in the code in your question.
Why not something like this:
public async Task CustomerRowSelectHandler(...)
{
await GetCustomerQuotes(...);
}
private async ValueTask GetCustomerQuotes(...)
{
string url = string.Format(#"....");
var http = new HttpClient(...);
HttpResponseMessage response = await http.GetAsync(url);
if (response.IsSuccessStatusCode)
customerQuotations = await response.Content.ReadFromJsonAsync<IEnumerable<Quotation>>() ?? Enumerable.Empty<Quotation>(); ;
// handle errors
}
Or even this, but you loose the error trapping.
customerQuotations = await http.GetFromJsonAsync<IEnumerable<Quotation>>(url);
You should also consider using the IHttpClientFactory to manage http instances.

Related

Web API Controller returning Task not always waits for task completion (puppeteer-sharp)

I have Web API controller which returns Task which is orginally created in external library service. I return Task in all the chain from serice to controller, but the problem is that when i make the HTTP call to that controller, first time when i have started the API (it`s always takes a bit longer first time) it returns the expected result perfectly, bu when I make the request second time and so on.. it returns some partial result.
When I debug it it always returns the expected correct result. Obvously there is something that is now awaited..
here is the code:
public async Task<HttpResponseMessage> DownloadBinary(string content)
{
byte[] recordToDown = await ExternalLibraryConverter.GetAsync(content);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(recordToDown)
};
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "Test file"
};
// added so Angular can see the Content-Disposition header
result.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/pdf");
return result;
}
and the service:
public static async Task<byte[]> GetAsync(string content)
{
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision)
.ConfigureAwait(false);
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
}).ConfigureAwait(false);
using (var page = await browser.NewPageAsync().ConfigureAwait(false))
{
await page.SetCacheEnabledAsync(false).ConfigureAwait(false);
await page.SetContentAsync(content).ConfigureAwait(false);
await page.AddStyleTagAsync("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700").ConfigureAwait(false);
// few more styles add
var result = await page.GetContentAsync().ConfigureAwait(false);
PdfOptions pdfOptions = new PdfOptions()
{
PrintBackground = true,
MarginOptions = new PuppeteerSharp.Media.MarginOptions {
Right = "15mm", Left = "15mm", Top = "20mm", Bottom = "20mm" },
};
byte[] streamResult = await page.PdfDataAsync(pdfOptions)
.ConfigureAwait(false);
browser.Dispose();
return streamResult;
}
}
There are a lot of await in the service with extenral library as you can see. I tried using ConfigureAwait(false) everywhere where await is used, but this didnt help neither.
I think you should not do a .ConfigureAwait on the controller level, look at this article for more information: https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html.
ASP.NET team dropped the use of SynchronizationContext, so using it in your controller is pointless.
As the article states, you should still use it on your service level, as you don't know whether or not a UI could plug itself to the service and use it, but on your WEB API, you can drop it.

Async, await and Task works in debug mode only

The code below begins with the first line calling 'LoadNames' from a .net page. If I'm not in debug mode the interviews variable is set to null. If I add a debug point there and step through it gets a value from the api.
It was getting interviews in UAT. I'm pointing it now at LIVE.
I'm thinking it's likely something unresolved asynchronously. The old api being slower probably made it appear like it was working, and adding debug will slow it down making it appear correct too.
Page.RegisterAsyncTask(new PageAsyncTask(LoadNames));
private async Task LoadNames()
{
VideoInterviewRepository videoRepository = await Repository.CreateClient();
IEnumerable<Api> interviews = await Repository.GetEntityList<Api>(EndPoints);
CODE HERE RUNS BUT FAILS BECAUSE THE ABOVE CODE RETURNS NULL
var interviewList = interviews.ToDictionary(o => o.id, o => o.name);
}
public static Task<VideoInterviewRepository> CreateClient()
{
var videoInterviewRepository = new VideoInterviewRepository();
return videoInterviewRepository.InitializeClient();
}
private async Task<VideoInterviewRepository> InitializeClient()
{
client = VideoInterviewHttpClient.GetClient(VideoInterviewEndPoints.baseUrl);
var bearer = await Authenticate.GetBearerTokenAsync(client);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearer);
return this;
}
public static async Task<string> GetBearerTokenAsync(HttpClient client)
{
var bearerResult = await client.SendAsync(requestToken);
var bearerData = await bearerResult.Content.ReadAsStringAsync();
bearerToken = JObject.Parse(bearerData)["access_token"].ToString();
}
public async Task<IEnumerable<T>> GetEntityList<T>(string path)
{
IEnumerable<T> model = await GetAndParseApiResponse<IEnumerable<T>>(path);
return model;
}
private async Task<T> GetAndParseApiResponse<T>(string path)
{
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
model = JsonConvert.DeserializeAnonymousType<T>(content, model);
}
return model;
}
I found the bug! All the async and awaits and Tasks were correct. The 3rd party didn't register the token on their end fast enough which is why by stepping through I caused a delay. I should have handled response.IsSuccessStatusCode.

Owin self hosting - limiting maximum time for request

How to efficiently limit request length timeout on server side ? I'm using Microsoft.Owin.Host.HttpListener and there are cases when (due to call to external service) serving request takes ridiculous amount of time. This is not a problem - but web server should give up sooner than - well never (I did some tests, but after 5 minutes I stopped it).
Is there a way how to limit time for serving single request (similar to <httpRuntime maxRequestLength="..." /> in IIS ecosystem) ?
Sample controller code:
public async Task<HttpResponseMessage> Get() {
// ... calls to 3pty services here
await Task.Delay(TimeSpan.FromMinutes(5));
}
Starting web server:
WebApp.Start(this.listeningAddress, new Action<IAppBuilder>(this.Build));
Note: I've read about limiting http listener, but that just limits incoming request properties, it doesn't cancel request that is slow due to slow server processing:
var listener = appBuilder.Properties[typeof(OwinHttpListener).FullName] as OwinHttpListener;
var timeoutManager = listener.Listener.TimeoutManager;
timeoutManager.DrainEntityBody = TimeSpan.FromSeconds(20);
timeoutManager.EntityBody = TimeSpan.FromSeconds(20);
timeoutManager.HeaderWait = TimeSpan.FromSeconds(20);
timeoutManager.IdleConnection = TimeSpan.FromSeconds(20);
timeoutManager.RequestQueue = TimeSpan.FromSeconds(20);
Related:
https://github.com/aspnet/AspNetKatana/issues/152
Conceptually "older" web server solutions - i.e. IIS are using one-thread-per-request separation and ThreadAbortException to kill slow requests. Owin is using different philosophy - i.e. it fires new task per request and forcibly cancelling task is best avoided. There are two sides of this problem:
shus client away if it takes too long
cancel server processing if it takes too long
Both can be achieved using middleware component. There also is a cancellation token provided directly by owin infrastructure for cases when client disconnects (context.Request.CallCancelled where context is IOwinContext)
If you're interested only in cancelling server flow ASAP when it takes to long, I'd recommend something like
public class MyMiddlewareClass : OwinMiddleware
{
// 5 secs is ok for testing, you might want to increase this
const int WAIT_MAX_MS = 5000;
public MyMiddlewareClass(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(
context.Request.CallCancelled))
{
source.CancelAfter(WAIT_MAX_MS);
// combined "client disconnected" and "it takes too long" token
context.Set("RequestTerminated", source.Token);
await Next.Invoke(context);
}
}
}
And then in controller
public async Task<string> Get()
{
var context = this.Request.GetOwinContext();
var token = context.Get<CancellationToken>("RequestTerminated");
// simulate long async call
await Task.Delay(10000, token);
token.ThrowIfCancellationRequested();
return "Hello !";
}
Shusing the client away is more complex. The middleware will look like this:
public static async Task ShutDownClientWhenItTakesTooLong(IOwinContext context,
CancellationToken timeoutToken)
{
await Task.Delay(WAIT_MAX_MS, timeoutToken);
if (timeoutToken.IsCancellationRequested)
{
return;
}
context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
}
public async Task ExecuteMainRequest(IOwinContext context,
CancellationTokenSource timeoutSource, Task timeoutTask)
{
try
{
await Next.Invoke(context);
}
finally
{
timeoutSource.Cancel();
await timeoutTask;
}
}
public override async Task Invoke(IOwinContext context)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(
context.Request.CallCancelled))
using (var timeoutSource = new CancellationTokenSource())
{
source.CancelAfter(WAIT_MAX_MS);
context.Set("RequestTerminated", source.Token);
var timeoutTask = ShutDownClientWhenItTakesTooLong(context, timeoutSource.Token);
await Task.WhenAny(
timeoutTask,
ExecuteMainRequest(context, timeoutSource, timeoutTask)
);
}
}

async getting no where

I am refactoring my ASP MVC code in session_start in Global.asax.cs with an async call to external service. I either get a white page with endless spinning in IE, or execution immediately returns to the calling thread. In the Session_start() when I tried .Result, I got white page with spinning IE icon. When I tried .ContinueWith(), the execution return to the next line which depends on the result from the async. Thus authResult is always null. Can someone help? Thanks.
This is from the Session_Start()
if (Session["userProfile"] == null) {
//call into an async method
//authResult = uc.checkUserViaWebApi(networkLogin[userLoginIdx]).Result;
var userProfileTask = uc.checkUserViaWebApi(networkLogin[userLoginIdx])
.ContinueWith(result => {
if (result.IsCompleted) {
authResult = result.Result;
}
});
Task.WhenAll(userProfileTask);
if (authResult.Result == enumAuthenticationResult.Authorized) {
This is from User_Controller class
public async Task < AuthResult > checkUserViaWebApi(string networkName) {
UserProfile _thisProfile = await VhaHelpersLib.WebApiBroker.Get < UserProfile > (
System.Configuration.ConfigurationManager.AppSettings["userWebApiEndpoint"], "User/Profile/" + networkName);
AuthResult authenticationResult = new AuthResult();
if (_thisProfile == null) /*no user profile*/ {
authenticationResult.Result = enumAuthenticationResult.NoLSV;
authenticationResult.Controller = "AccessRequest";
authenticationResult.Action = "LSVInstruction";
}
This is helper class that does the actual call using HttpClient
public static async Task<T> Get<T>(string baseUrl, string urlSegment)
{
string content = string.Empty;
using(HttpClient client = GetClient(baseUrl))
{
HttpResponseMessage response = await client.GetAsync(urlSegment.TrimStart('/')).ConfigureAwait(false);
if(response.IsSuccessStatusCode)
{
content = await response.Content.ReadAsStringAsync();
}
return JsonConvert.DeserializeObject<T>(content);
}
It doesn't make sense to call User_Controller from Session_Start.
You want to call VhaHelpersLib directly inside Session_Start, if VhaHelpersLib doesn't have any dependencies.
Since Session_Start is not async, you want to use Result.
var setting = ConfigurationManager.AppSettings["userWebApiEndpoint"];
UserProfile profile = await VhaHelpersLib.WebApiBroker.Get<UserProfile>(
setting, "User/Profile/" + networkName).Result;
if (profile == enumAuthenticationResult.Authorized) {
...
}

Async WCF client calls with custom headers: This OperationContextScope is being disposed out of order

I'm calling a WCF service from a WinRT app. The service requires that some headers are set for the authentication. The problem is that if I do multiple calls to the service simultaneously, I get the following exception:
This OperationContextScope is being disposed out of order.
The current code looks like the following:
public async Task<Result> CallServerAsync()
{
var address = new EndpointAddress(url);
var client = new AdminServiceClient(endpointConfig, address);
using (new OperationContextScope(client.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = GetHeader();
var request = new MyRequest(...);
{
context = context,
};
var result = await client.GetDataFromServerAsync(request);
}
}
I found the following comment from the docs:
Do not use the asynchronous “await” pattern within a OperationContextScope block. When the continuation occurs, it may run on a different thread and OperationContextScope is thread specific. If you need to call “await” for an async call, use it outside of the OperationContextScope block.
So it seems I'm clearly calling the service incorrectly. But what is the correct way?
According to Microsoft documentation:
Do not use the asynchronous "await" pattern within a OperationContextScope block. When the continuation occurs, it may run on a different thread and OperationContextScope is thread specific. If you need to call "await" for an async call, use it outside of the OperationContextScope block.
So the simplest proper solution is:
Task<ResponseType> task;
using (new OperationContextScope(client.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = GetHeader();
var request = new MyRequest(...);
{
context = context,
};
task = client.GetDataFromServerAsync(request);
}
var result = await task;
This is a known "issue" and for anyone stuck with this, you can simply run your call synchronously. Use GetAwaiter().GetResult(); instead since it doesn't schedule a Task at all, it simply blocks the calling thread until the task is completed.
public Result CallServer()
{
var address = new EndpointAddress(url);
var client = new AdminServiceClient(endpointConfig, address);
using (new OperationContextScope(client.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = GetHeader();
var request = new MyRequest(...);
{
context = context,
};
return client.GetDataFromServerAsync(request).GetAwaiter().GetResult();
}
}
Everything seems to work quite well with the following code:
public async void TestMethod()
{
var result = await CallServerAsync();
}
public Task<Result> CallServerAsync()
{
var address = new EndpointAddress(url);
var client = new AdminServiceClient(endpointConfig, address);
using (new OperationContextScope(client.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = GetHeader();
var request = new MyRequest(...);
{
context = context,
};
return client.GetDataFromServerAsync(request);
}
}

Categories

Resources