Two parallel ajax requests to Action methods are queued, why? - c#

I'm developing a video website using ASP.NET MVC.
One functionality I want to have in my application is transocding video. But as the transcoding process could be very time-consuming, I want to show the progress of that process to the client user.
So, my schema is to use one controller action to handle the whole transcoding process and write its progress into a file stored on the server. Meanwhile I use Ajax to call another controller action to read the specified file, retrieve the progress information and send it back to the client for display every 2 seconds during the transcoding process.
To fulfill my plan, I have written the following code:
Server Side:
public class VideoController : Controller
{
//Other action methods
....
//Action method for transcoding a video given by its id
[HttpPost]
public async Task<ActionResult> Transcode(int vid=0)
{
VideoModel VideoModel = new VideoModel();
Video video = VideoModel.GetVideo(vid);
string src = Server.MapPath("~/videos/")+video.Path;
string trg = Server.MapPath("~/videos/") + +video.Id+".mp4";
//The file that stores the progress information
string logPath = Server.MapPath("~/videos/") + "transcode.txt";
string pathHeader=Server.MapPath("../");
if (await VideoModel.ConvertVideo(src.Trim(), trg.Trim(), logPath))
{
return Json(new { result = "" });
}
else
{
return Json(new { result = "Transcoding failed, please try again." });
}
}
//Action method for retrieving the progress value from the specified log file
public ActionResult GetProgress()
{
string logPath = Server.MapPath("~/videos/") + "transcode.txt";
//Retrive the progress from the specified log file.
...
return Json(new { progress = progress });
}
}
Client Side:
var progressTimer = null;
var TranscodeProgress = null;
// The function that requests server for handling the transcoding process
function Transcode(vid) {
// Calls the Transcode action in VideoController
var htmlobj = $.ajax({
url: "/Video/Transcode",
type: "POST",
//dataType: 'JSON',
data: { 'vid': vid },
success: function(data)
{
if(data.result!="")
alert(data.result);
}
else
{
//finalization works
....
}
}
});
//Wait for 1 seconds to start retrieving transcoding progress
progressTimer=setTimeout(function ()
{
//Display progress bar
...
//Set up the procedure of retrieving progress every 2 seconds
TranscodeProgress = setInterval(Transcoding, 2000);
}, 1000);
}
//The function that requests the server for retrieving the progress information every 2 seconds.
function Transcoding()
{
//Calls the GetProgress action in VideoController
$.ajax({
url: "/Video/GetProgress",
type: "POST",
//dataType: 'JSON',
success: function (data)
{
if (data.progress == undefined || data.progress == null)
return;
progressPerc = parseFloat(data.progress);
//Update progress bar
...
}
});
}
Now the Client-side code and the Transcode action method all work fine. The problem is that the GetProgress method will never get called until the Transcode action finishes its whole procedure. So what's wrong with my code? How can I modify it to make those two actions work spontaneously so as to achieve my goal?
Update
Based on Alex's answer, I found that my problem is caused by the session lock mechanism of Asp.Net framework. So disabling the SessionState of my VideoController or setting it as read-only does make the controller responses to the request for retrieving transcoding progress when the action method of transcoding video is being executed. But because I use Session in my VideoController to store some variables for use across multiple requests, this way couldn't be a suitable solution for my problem. Is there a better way to solve it?

You misunderstood the whole point about async/await. It doesn't change the fact that for each single request, there is a single response that is returned. When you call await in your action, nothing is returned to client yet. The only thing it does (in a very high level abstraction) is to release the current thread that handles this request to a thread pool so it could be used for processing other requests. So basically it allows you to use your server resources more efficiently since there are no threads that are wasted waiting for long I/O operations to complete. Once the I/O operation is completed the the execution of the action (that called for await) continued. Only at the end of an action the response is sent to the client.
As for you scenario, if it is a long running task, I would use some kind of background processing solution such as Hangfire and use SignalR to push updates from server.Here is an example
You can also implement something similar on your own (example).
UPDATE:
As #Menahem stated in his comment I maybe misinterpreted part of your question.
Request queuing issue may be caused by incorrect configuration of SessionStateBehavior. Since MvcHandler handler which is used by ASP.NET MVC is marked with IRequiresSessionState interface, only one request at time could be processed per session. In order to change that, make you controller sessionless (or ar least make sure that you are not writing into session in this controller) and mark it with
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)] attribute.

File creation is blocking call. In other words, until first thread will not close file, second one which makes report will not be able to read contents of that file. As workaround you can create files with percentage of progress. For example movie1-5-percent.txt, movie1-10-percent.txt, movie1-15-percent.txt etc, in order to avoid blocking calls to file system. Then you can check, if for movie1 there is file movie1-15-percent.txt, then you can report to ajax call, that 15 percent of movie was converted. Or choose another non blocking storage. For example you can report result to db in first thread, and read results from db in another.

Related

Run a method async while redirected to other pages

I have a asp.net MVC application which uses J query and Ajax. And one of cshtml page which includes a process running for a long time, so i made a async method and using ajax i was able to trigger it so hoping that the user can do some other work in a different page while the long process running in the background.
Its working fine when I'm working in the same page. But when i redirect to another page while the code is running in back end I'm not getting any output. Is it not possible to redirect while the another method is running? if so how can i get this done.
My c# contoller
public async System.Threading.Tasks.Task<JsonResult> TestAsyncPro(string ComponentId)
{
try
{
await System.Threading.Tasks.Task.Delay(4000);
return Json(new { Success = true, Error = "" }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(new { Success = false, Error = ex.Message }, JsonRequestBehavior.AllowGet);
}
}
Ajax Call
$('#testasnc').click(function ()
{
$.ajax({
url: '../AccountOpening/TestAsyncPro',
type: 'POST',
dataType: 'json',
cache: false,
async: true,
data: { ComponentId: '21' },
success: function (data) {
if (data.Success == true)
{
$(window).scrollTop(0);
$("#alrtTestAsnc").show();
setTimeout(function () { $("#alrtTestAsnc").hide(); }, 5000);
}
}
});
var url = $("#RedirectTo").val();
location.href = url;
});
so i made a async method and using ajax i was able to trigger it so hoping that the user can do some other work in a different page while the long process running in the background.
That's not what async is for.
What you really want is an independent background worker. ASP.NET works by handling HTTP requests, and you want some work done outside an HTTP request (i.e., after the HTTP request completes). Thus, the work you want to do is not a natural fit for ASP.NET.
I usually recommend a proper distributed system design for this kind of situation. This means adding a reliable queue (Azure Queue / Amazon SQS / etc), and a separate backend processor (Azure Function / Amazon Lambda / ASP.NET Core background service / Win32 service / etc). Then the ASP.NET handler will enqueue the background work to do to the queue, and return a redirect; the backend processor will pick up the work from the queue and actually execute it.
Hangfire is a low-entry solution; it uses a database as a reliable queue and has an in-process backend processor (similar to ASP.NET Core background service hosted in an ASP.NET Core HTTP application). There are some tradeoffs with this approach (e.g., you can't scale your backend independently), but it's an OK solution if you're not on ASP.NET Core yet.

Async MVC.NET action method blocks any other HTTP requests

(I apologize for changing the question)
The following snippet is from a MVC.NET controller (.NET: v4.5; AspNet.MVC: v5.2.3) .
After LongOperation is called, it:
Spawn a process
Waits for its completion
Monitors a few LOG files
Uses SignalR to notify browser of the progress from the LOG files
(I have omitted the code for simplicity)
All this works, only while LongOperation is running, no other HTTP requests are handled by the controllers.
They get handled after the LongOperation completes and the action method returns result to the AJAX call.
What am I messing up?
Thank you in advance.
Update (for #angelsix comment):
Here is a simplified setup:
I have removed async/await as advised
Added breakpoints as advised
Verified they are hit as explained in the above
Basically: same result, see the console.log-ed text and timestamps
Will appreciate any help from the community.
Thank you in advance!
GUI and log
Action methods in the Controller
[AjaxOnly]
public ActionResult _RunLongOperation(string hubId)
{
try
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
ProgressNotifierHub.Notify(hubId, string.Format("Notification from _RunLongOperation {0}", i));
}
return new HttpStatusCodeResult(HttpStatusCode.OK, "_RunLongOperation : OK");
}
catch (Exception)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "_RunLongOperation : NOK");
}
}
[AjaxOnly]
public ActionResult _RunAnotherOperation(string hubId)
{
return new HttpStatusCodeResult(HttpStatusCode.OK, "_RunAnotherOperation : OK");
}
Razor View (partial) and javascript with SignalR hub setup Ajax calls
<script src="~/signalr/hubs"></script>
#{
Layout = null;
}
<button id="longOperationBtn" type="button" class="t-button" style='width: 155px'>Long Operation</button>
<button id="anotherOperationBtn" type="button" class="t-button" style='width: 155px'>Another Operation</button>
<script type="text/javascript">
$(function () {
setupEventHandlers();
setupProgressNorificator();
});
function setupEventHandlers() {
$('#longOperationBtn').click(function (event) {
requestOperation('_RunLongOperation')
});
$('#anotherOperationBtn').click(function (event) {
requestOperation('_RunAnotherOperation')
});
}
function requestOperation(method) {
trace(method + ' requested');
$.ajax({
url: '/Profiles/Validate/' + method,
type: 'GET',
data: { hubId: $.connection.hub.id },
contentType: 'application/json; charset=utf-8',
success: function () {
trace(method + ' completed');
},
error: function () {
trace(method + ' failed');
}
});
}
function setupProgressNorificator(profileId) {
var hub = $.connection.progressNotifierHub;
hub.client.notify = function (notification) {
console.log(notification);
};
$.connection.hub.start();
}
function trace(s) {
console.log('[' + new Date().toUTCString() + '] ' + s);
}
</script>
It looks like you are running the client "test" on Chrome. I'm not sure what the 2018 limitation is (it seems to change from release to release), but browsers do limit the number of concurrent connections allowed to the same host. I believe Chrome's limit is 6 or 8.
The log you posted appears to be from the client side. If it is, the behavior you are seeing could actually be the client waiting for a free connection to connect to the server - the problem may not be with ASP.NET at all, but rather with how you are testing.
It would be easy to validate - write another test that you can run concurrently with your current test in a separate browser window that just calls the "short" operation. If it has latency, I'm wrong. If it doesn't, well hopefully I've helped out!
The issue
Your response does not mention anything about caching. So I suspect the browser is caching the response and using that.
Verify
To verify press F12 in the browser to open developer tools and then see if the second response shows a status of 304 or states Cached
Solve
To prevent caching on the action, inside the action you want to do this
Prevent Caching in ASP.NET MVC for specific actions using an attribute
According to this:
Some ... protocols like HTTP Polling (XHR) use up to two simultaneous connections per ... client. It is important to understand that the maximum number of connections is per browser and not per browser tab. Attempting to run multiple clients within the same browser might cause this limit to be reached.
In my case, SignalR client was using long polling. A fried reported that when Web sockets are used, there was no blocking issue. Closing the subject.
Thanks for the help Joe.
You need to set your controller to have read-only session state behavior. Then you can do ajax requests to the controller during running a long controller method. In other case (as the one that you are complaining about) the requests will be queued and call all at once after finishing the controller action call. Just put this before controller class definition
[SessionState(SessionStateBehavior.ReadOnly)]

SQL Update then Select, Selects the original value

I have a super basic EF method that basically just does:
var obj = context.Set<Objs>().Single(x => x.ID = id);
obj.varBinaryMax = largeByteArray;
await context.SaveChangesAsync();
The method is async Task<...> UpdateObj()
The method calling THAT is an async Task<...>, which has an await when calling UpdateObj().
There are awaits all the way down to a WebAPI Call which is:
public async Task<HttpResponseMessage> UpdateObj(...)
Of course the call to the method chain in the WebAPI is also awaited.
When stepping through the code, everything is definitely slow, because I'm talking about multiple megabytes of Byte[] for this update. That may or may not be the issue.
The UI is using JQuery to make this call:
$.ajax({
type: "POST",
url: 'https://dubs.website.com/api/Object/Edit/' + someID,
data: someImageBytes,
success: function () {
alert('lol, all done');
},
error: function () {
alert('lol, error');
}
});
The problem is... if I refresh the page, which then queries the database for the image bytes, I get back an unaltered old image. Unless I wait about 10 seconds, then refresh, then I get back the updated image.
But, for sure, my update calls all finished, were awaited, and updated the database. So why, for some number of seconds, am I being returned an old object? My WebAPI call to retrieve the object is NOT Caching the original object.
Is SQL server taking a while to update that VarBinary(MAX) field, even after it returns completion?

Update label while a method is running

How do I update a label in a aspx page while a method is running? Perhaps using AJAX (update panel)?
private void button_Click(object sender, EventArgs e)
{
doThings1();
label.Text = "Status1";
doThings2();
label.Text = "Status2";
doThings3();
label.Text = "Done";
}
I want to show step by step. While the method is running when the doThings1() is done, shows "Status1", doThings2() is done, shows "Status2"... In this way, the label doesn't show "Status1" and "Status2", just "Done" when the process is finished. I'd like to show step by step.
This is not an easy thing to do, the way it is in a desktop application. You need to start an asynchronous operation that will continue after the request ends, you'll need to have the client continually poll the server for updates as to the progress, and the server side asynchronous code will need to update some sort of share state (i.e. session, a database, view state, etc.) that the polling method can read the progress from. All around it's quite inefficient (especially if you have a lot of users doing this) and takes some time to write. Here is an example on MSDN that does this, to give you an idea of what's involved.
The rule is: 1 request --> one response.
Different approach:
You can these methods execute with 3 asyncron javascript call and set the labels' text at the success callback.
http://api.jquery.com/jQuery.ajax/
Example:
$.ajax({
type: "POST",
url: "URL.asmx/doThings1",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(result) {
// result will be "done" from the function of webservice below.
// set the first label text
},
error: function(xmlHttpRequest, status, err) {
alert(xmlHttpRequest.statusText + " " + xmlHttpRequest.status + " : " + xmlHttpRequest.responseText);
}
});
Repeat these calls 3 times and do your modifications in different functions.
You can handle your buttonclick at client side with jquery or pure javascript.
You can use a webservice or generic handler to execute server side methods.
How to create webservice
[WebMethod]
public string doThings1()
{
return "done";
}
It sounds like you want to show the progress of some task that is running on the server. The signalr library will allow you to send real time updates to the client from the server. So anytime the task completed a stage (Status1, Status2, etc) of the task, it would send an update to the listening clients with the new status.
You could also have some javascript request the task status from the server every few seconds and display it to the user.

Asynchronous Web Methods

I have a client/service.
The service has a method that takes a long time to do (it interacts with a database).
I'm call this method via a AJAX request from the page to the client, then to the service and back.
My service code:
[WebMethod]
public static string LookupUPC(string sessionId, string upc) {
string response = "";
var client = new SmartShopService.SmartShopInterfaceClient();
try {
response = client.LookupUPC(sessionId, upc);
}
catch (Exception e) {
throw e;
}
finally {
if (client.State == System.ServiceModel.CommunicationState.Faulted)
client.Abort();
else
client.Close();
}
return response;
}
It is called from the page by an AJAX request
for(var i = 0;i<10; i++){
$.ajax({
type: "POST",
url: "SmartShopGUI.aspx/LookupUPC",
contentType: "application/json; charset=utf-8",
data: DataCreator(allData),
dataType: "json",
success: function (result) {
$(upcName).html(result.d);
},
error: AjaxFailed
});
}
Now, this is done asynchronously on the page, but the client is sending the requests synchronously. I want to change it so that if it asks for 10 all at once, it'll send 10 different requests to the service.
http://www.screencast-o-matic.com/watch/cX1Qo8qV2
Here is a video that might help.
Remove the reliance on Session in the webmethod, you'll probably find that session access is serial and that is what the block is.
http://msdn.microsoft.com/en-us/library/ms178581.aspx
Concurrent Requests and Session State
Access to ASP.NET session state is
exclusive per session, which means
that if two different users make
concurrent requests, access to each
separate session is granted
concurrently. However, if two
concurrent requests are made for the
same session (by using the same
SessionID value), the first request
gets exclusive access to the session
information. The second request
executes only after the first request
is finished. (The second session can
also get access if the exclusive lock
on the information is freed because
the first request exceeds the lock
time-out.) If the EnableSessionState
value in the # Page directive is set
to ReadOnly, a request for the
read-only session information does not
result in an exclusive lock on the
session data. However, read-only
requests for session data might still
have to wait for a lock set by a
read-write request for session data to
clear.
You would do better to create a BatchLookup API that can handle a block of requests all in one go. Any connection between a browser and a server will be limited as to how many simultaneous requests you can make and each round trip is a slow operation itself.
I suspect that this isn't a server-side issue at all but the browser connection limitation that you are hitting. Even if you fix the server-side to handle two simultaneous requests the browser isn't going to fire off all 10 of them at once. See for example this article on the topic: http://www.ajaxperformance.com/2006/12/18/circumventing-browser-connection-limits-for-fun-and-profit/
BTW, how can looking up a single UPC code in a database take so long? Do you have it indexed properly?

Categories