await blocks the request - c#

I have an action in my controller in which there is an async function call
public async Task<ActionResult> Subscribe(AspNetUser user, string newUploadPath)
{
//do some work
await DocServiceImpl.CopyUserAllModels(user, newUploadPath);
//do some work
return RedirectToAction("List", "ClientDashboard");
}
and this function uploads a lot of files to Azure CDN so it takes too long.
The problem is that the client who makes this request has to wait until the CopyUserAllModels function finishes, because all this waiting time the client sees his page is reloading in the browser.
I've tried not to wait at all and used without await
DocServiceImpl.CopyUserAllModels(user, newUploadPath);
but I've searched that this is a bad experience, and except it,
in this case I find that some part of the files haven't been uploaded at all, so
without await it does not work properly in my case(I couldn't understand why).
My problem is: How to finish the request earlier, and then after it do all the work in CopyUserAllModels function?

Related

Async Await control flow in Web API

webcontroller {
async Task<ActionResult<string>> doSomething() {
var stringResult = await doSomethingAsync();
return stringResult;
}
}
what will be control flow here? will the controller return dummy response (ActionResult) to client after reaching doSomething() method call or the control remain in the web controller and return the stringResult to client? consider doSomething() is doing some network intensive tasks which might take more time to complete. Can anyone please explain the same to me if possible? Thanks in Advance!
will the controller return dummy response (ActionResult) to client
after reaching doSomething() method call or the control remain in the web controller and return the stringResult to client
It will not return anything to the client until doSomething method finished.
consider doSomething() is doing some network intensive tasks which
might take more time to complete
In this case you will have timeout on the client.
You have to start background job. Return to the client that task has been started. Then tell somehow to the client that task is finished.
Another source of information: Long running task in WebAPI
I recommend reading an article I wrote about how async works on ASP.NET.
will the controller return dummy response (ActionResult) to client after reaching doSomething() method call or the control remain in the web controller and return the stringResult to client?
When doSomethingAsync returns an incomplete task, then the await in doSomething will also return an incomplete task. Then the ASP.NET runtime (asynchronously) waits for that task to complete before sending the response.
await in ASP.NET yields to the thread pool; it does not yield to the client.

Async progress in ASP .NET Core middleware?

I have a web-api with mvc that's doing a bunch of startup initialization which will take a few minutes. I want the url to respond to a request during this time with a progress-indicator. My thinking was to use a middleware to accomplish this something like this:
public async Task Invoke(HttpContext httpContext)
{
await httpContext.Response.WriteAsync("Loading...");
await Task.Delay(5000); // the initialization-stuff (which is not started here but just waited on)
httpContext.Response.Clear();
await _next(httpContext); // continue to my MVC-page
}
However this does not seem to work (ERR_INCOMPLETE_CHUNKED_ENCODING). How do I properly clear/reset the respons so that I can write a new real response once the initialization is done.
I resorted to something like this instead (good enough):
public async Task Invoke(HttpContext httpContext)
{
if (!task.IsCompleted)
await httpContext.Response.WriteAsync("Loading...");
else
await _next(httpContext); // continue...
}
Once you have sent data to the client you can't take it back. You can't replace an existing page, only append.
You can therefore do two things:
You delay the response until initialization is complete (likely not feasible).
You send something else and end the request. You could make the page that you are sending poll the server using AJAX to see if initialization has been completed. Then, the page can reload itself.
Create a new API endpoint that replies with the initialization status. Make page page poll that endpoint every few seconds.

Handling aborted requests in ASP.NET Core

Say I have the following action
public async TaskIActionResult> UploadFile()
{
var form = await Request.ReadFormAsync();
// do something with form contents
}
and it's called with a large, say 100MB, file.
How can I handle what happens when a user cancels/aborts the request? (e.g it's uploaded 50MB and user stops the request) Is there a way to detect this? Is this even a valid question to be asking?

MVC HttpClient multiple post requests, get mismatched responses

The scenario
I need to show N reports on a web page. The reports need to be requested to an external service. The time for the service to generate a report can vary from 2 seconds to 50 seconds, depending on the requested content.
To call the service I use HttpClient in an async action. To generate 1 report I call the service once. To generate 5 reports I call it 5 times and so on.
The Problem
Let's suppose we request 3 reports BigReport, MediumReport and SmallReport with a known relative generation time of 1 minute, 30 seconds and 2 seconds, and we call the service in the following order:
BigReport, MediumReport, SmallReport
The result of the HttpCalls will be as following:
HttpCall response for BigReport returns SmallReport (which is the quickest to be generated)
MediumReport will be correct
SmallReport response will contain the BigReport (which is the longest and the last)
Basicly, although the HttpCalls are different, for the fact they are made over a very short period of time, and they are still "active", the server will repond based on first arrived, first served, instead of serving each call with its exact response.
The Code
I have a Request controller with an async action like this:
public async Task<string> GenerateReport(string blockContent)
{
var formDataContent = new MultipartFormDataContent
{
AddStringContent(userid, "userid"),
AddStringContent(passcode, "passcode"),
AddStringContent(outputtype, "outputtype"),
AddStringContent(submit, "submit")
};
var blockStream = new StreamContent(new MemoryStream(Encoding.Default.GetBytes(blockContent)));
blockStream.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"" + filename + "\"");
formDataContent.Add(blockStream);
using (var client = new HttpClient())
{
using(var message = await client.PostAsync(Url, formDataContent))
{
var report = await message.Content.ReadAsStringAsync();
return report;
}
}
}
The action is being called from a view via Ajax, like this
//FOREACH BLOCK, CALL THE REPORT SERVICE
$('.block').each(function(index, block) {
var reportActionUrl = "Report/GenerateReport/"+block.Content;
//AJAX CALL GetReportAction
$(block).load(reportActionUrl);
});
Everything works fine if I covert the action from async to sync, by removing async Task and instead of "awaiting" for the response, I just get result as
var result = client.PostAsync(Url, formDataContent).Result.
This will make everything run synchronously and working fine, but the waiting time for the user, will be much longer. I would really like to avoid this by making parallel calls or similar.
Conclusions and questions
The problem itself make sense, after inspecting it also with Fiddler, as we have multiple opened HttpRequests pending almost simultaneously.
I suppose I need a sort of handler or something to identify and match request/response, but I don't know what's the name of the "domain" I need to look for. So far, my questions are:
What is the technical name of "making multiple http calls in parallel"?
If the problem is understandable, what is name of the problem? (concurrency, parallel requests queuing, etc..?)
And of course, is there any solution?
Many thanks.
With a "bit" of delay, I post the solution.
The problem was that the filename parameter was incorrectly called filename instead of blockname. This was causing the very weird behaviour, as a file could have had many blocks.
The lesson learned was that in case of very weird behaviour, in this case with a HttpClient call, analyse all the possible parameters and test it with different values, even if it doesn't make too much sense. At worst it can throw an error.

Web API allow only single async task

What is a proper scenario for handling only single async action? For example I need to import large file and while it being imported I need to disable that option to ensure that second import not triggered.
What comes in mind that:
[HttpPost]
public async Task<HttpResponseMessage> ImportConfigurationData()
{
if (HttpContext.Current.Application["ImportConfigurationDataInProcess"] as bool? ?? false)
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Task still running");
HttpContext.Current.Application["ImportConfigurationDataInProcess"] = true;
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
await Request.Content.ReadAsMultipartAsync(provider);
//actual import
HttpContext.Current.Application["ImportConfigurationDataInProcess"] = false;
Request.CreateResponse(HttpStatusCode.OK, true)
}
But it seems like very hard-coded solution. What is a proper way of handling that?
Another thing it is not properly works on client side at it still waits for a response. So is it possible for user just to send that file to server and not wait unlit it will finishes but reload page after file sent to server without waiting while await stuff will finish.
async does not change the HTTP protocol (as I explain on my blog). So you still just get one response per request.
The proper solution is to save a "token" (and import data) for the work in some reliable storage (e.g., Azure table/queue), and have a separate processing backend that does the actual import.
The ImportConfigurationData action would then check whether a token already exists for that data, and fault the request if found.

Categories