I have a Action method in my controller that continues to redirect back to itself until some process is finished. Much like this:
public async Task<ActionResult> Index(string id)
{
var result = await GetAsyncData(id);
if(!result.processed)
{
return RedirectToAction("Index","Controller", new {id=id});
}
}
Everything works as expected, as soon as result.processed is true the redirect no longer occurs. My only concern is with resource handling. Would this code continue to execute if result.processed is always false, even if the user closes the browser.
If it was always false, you would eventually get an Error 310 (net::ERR_TOO_MANY_REDIRECTS). Note that this could also occur if your GetAsyncData lasts too long. I also don't see why you would write an async method that would wait for its finish while redirecting.
Related
I got to open a pop-up which will display the list of Customers. I'm Using DreamFactory for all CRUD operations. But DreamFactory has got all operation as async operation and while loading partial view it gives error:
HttpServerUtility.Execute blocked while waiting for an asynchronous operation to complete.
I'm using below code to load the partial view -
#Html.Action("GetAddressListForPartial", "Customer", new { CustomerID = Model.CustomerID })
This is my controller method
public async Task ActionResult CustomerList()
{
Models.Customers oCustomers = await new DataAccessLayer.DataAccess().GetCustomerList(databaseApi);
return View("CustomerList", oCustomers);
}
Please let me know if there any workaround to handle this situation.
Thanks
Your issue is not just related to DreamFactory. With ASP.Net MVC 5 and earlier, child actions can't be executed when your action method use async.
Child action must run synchronously. If you want your code to work you must remove async keywork hence removing Task. At the end you must have this :
public ActionResult CustomerList()
{
Models.Customers oCustomers = new DataAccessLayer.DataAccess().GetCustomerList(databaseApi).Result;
return View("CustomerList", oCustomers);
}
With ASP.Net MVC 6, ViewComponent will allow child action to be executed asynchronously.
Suppose I have an Action like below that I want to return the View asap and continue doing some work in the background thread.
public async Task<ActionResult> Index()
{
Debug.WriteLine("Inside Index");
var newCustomer = new Customer
{
Name = "Ibrahim"
};
Task.Run(() => SaveCustomer(newCustomer));
Debug.WriteLine("Exiting Index");
return View();
}
private async Task SaveCustomer(Customer NewCustomer)
{
Debug.WriteLine("Started Saving Customer");
await Task.Delay(2000);
Debug.WriteLine("Completed Saving Customer");
}
I do get the output as intended which is:
Inside Index
Exiting Index
Started Saving Customer
Completed Saving Customer
But what bothers me is that I get a warning that my Index action will run synchronously regardless and I should put an await but then the view is returned after SaveCustomer is completed and the purpose is defeated.
How am I doing this wrong? Any suggestion is appreciated.
But what bothers me is that I get a warning that my Index action will run synchronously
How am I doing this wrong?
Don't force asynchrony from the top down. Instead, start with naturally-asynchronous operations at the lowest level (e.g., EF6 database access), and allow the asynchrony grow from the lowest-level code upward.
Also, on ASP.NET, you should strongly avoid Task.Run.
Applying these two principles results in an Index method like this:
public async Task<ActionResult> Index()
{
Debug.WriteLine("Inside Index");
var newCustomer = new Customer
{
Name = "Ibrahim"
};
await SaveCustomer(newCustomer);
Debug.WriteLine("Exiting Index");
return View();
}
but then the view is returned after SaveCustomer is completed and the purpose is defeated.
Not at all. The purpose of asynchronous ASP.NET code is not to return early to the client. async and await do not change the HTTP protocol. await on the server side yields to the thread pool, not the client.
If you need to return early (and most people don't - they only think they "need" to), then you should use one of the established patterns for returning early (as I describe on my blog). Note that the only proper (i.e., fully reliable) solution entails setting up a reliable queue with an independent background process.
Your Index does not make use of any async feature at all. Why did you mark it async? You must be misunderstanding something, not sure what. Remove the async Task specification.
You get that compiler warning because there is nothing asynchronous in your Index() method. Your Task.Run(() => SaveCustomer(newCustomer)); line means Fire And Forget (non awaited task) - this is very different than asynchronous code. Index() is completely synchronous, while creating a "side Task" to execute sometime in the future. As the other answer mentioned - you could just as well remove the async mark from your method - it's not async.
I have a MVC website where I am trying to call a method with async. My code is like below:
Views
Submit Download
$(document).on('click', '.submit-download', function (evt) {
var submit_url = '/SubmitDownloads/24589;
$.ajax({
type: 'POST',
url: submit_url,
success: function (data) {
if (data.Item1)
location.reload();
}
});
});
Controller
[HttpPost]
public async Task<JsonResult> SubmitDownloads(int id)
{
var respository = new WorkflowRepository();
var result = await respository.SubmitAsync(id);
return Json(result, JsonRequestBehavior.AllowGet);
}
Repository Method
//db service call which will take much longer time
public async Task<Tuple<bool, string>> SubmitAsync(id)
{
//long running method here
await Task.Delay(20000);
return new Tuple<bool, string>(true, "done with " + id);
}
When user clicks on the 'Submit Download' link in Views, it complete the entire function quickly as its supposed to do and page shows responsive like scrollable, menu shows fine. But when I click on any link in the page, it waits till the entire operation is finished (20 seconds) and then redirect to respective URL.
If I change the Task.Delay to 50 seconds, link click takes 50 seconds to redirect.
Can you please guide me what I am missing here?
But when I click on any link in the page, it waits till the entire operation is finished (20 seconds) and then redirect to respective URL.
Asynchronous controller methods don't make the HTTP interaction async, just the interaction from the web server to the web application async. The idea is that a high volume web server can free up threads to service other requests while long-running requests are doing their thing.
When you click on a link, the browser needs to wait for a response before, well, processing that response (displaying the page). There's no way to display a page without waiting for that page to be sent from the web server.
On GET request I run (something like):
public ActionResult Index(void) {
webClient.DownloadStringComplete += onComplete;
webClient.DownloadStringAsync(...);
return null;
}
I see that onComplete isn't get invoked until after Index() has finished execution.
I can see that onComplete is invoked on a different thread from one Index was executed on.
Question: why is this happening? why is webClient's async thread is apparently blocked until request handling thread is finished?
Is there a way to fix this without starting new thread from ThreadPool (I tried this, and using thread pool does work as expected. Also webClient's callback does happen as expected if DownloadStringAsync is called from a ThreadPool's thread).
ASP.NET MVC 3.0, .NET 4.0, MS Cassini dev web server (VS 2010)
EDIT: Here is a full code:
public class HomeController : Controller {
private static ManualResetEvent done;
public ActionResult Index() {
return Content(DownloadString() ? "success" : "failure");
}
private static bool DownloadString() {
try {
done = new ManualResetEvent(false);
var wc = new WebClient();
wc.DownloadStringCompleted += (sender, args) => {
// this breakpoint is not hit until after Index() returns.
// It is weird though, because response isn't returned to the client (browser) until this callback finishes.
// Note: This thread is different from one Index() was running on.
done.Set();
};
var uri = new Uri(#"http://us.battle.net/wow/en/character/blackrock/hunt/simple");
wc.DownloadStringAsync(uri);
var timedout = !done.WaitOne(3000);
if (timedout) {
wc.CancelAsync();
// if this would be .WaitOne() instead then deadlock occurs.
var timedout2 = !done.WaitOne(3000);
Console.WriteLine(timedout2);
return !timedout2;
}
return true;
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
return false;
}
}
I was curious about this so I asked on the Microsoft internal ASP.NET discussion alias, and got this response from Levi Broderick:
ASP.NET internally uses the
SynchronizationContext for
synchronization, and only one thread
at a time is ever allowed to have
control of that lock. In your
particular example, the thread running
HomeController::DownloadString holds
the lock, but it’s waiting for the
ManualResetEvent to be fired. The
ManualResetEvent won’t be fired until
the DownloadStringCompleted method
runs, but that method runs on a
different thread that can’t ever take
the synchronization lock because the
first thread still holds it. You’re
now deadlocked.
I’m surprised that this ever worked in
MVC 2, but if it did it was only by
happy accident. This was never
supported.
This is the point of using asynchronous processing. Your main thread starts the call, then goes on to do other useful things. When the call is complete, it picks a thread from the IO completion thread pool and calls your registered callback method on it (in this case your onComplete method). That way you don't need to have an expensive thread waiting around for a long-running web call to complete.
Anyway, the methods you're using follow the Event-based Asynchronous Pattern. You can read more about it here: http://msdn.microsoft.com/en-us/library/wewwczdw.aspx
(edit) Note: Disregard this answer as it does not help answer the clarified question. Leaving it up for the discussion that happened under it.
In addition to the chosen answer, see this article for further details on why the WebClient captures the SynchronizationContext.
http://msdn.microsoft.com/en-gb/magazine/gg598924.aspx
I am trying to provide a progress monitoring mechanism for a longish-running request implemented by an AsyncController. A web page invokes (via JQuery $.post) the asynchronous StartTask action on the following controller...
[NoAsyncTimeout]
public class TaskController: AsyncController
{
[HttpPost]
public void StartTaskAsync()
{
AsyncManager.OutstandingOperations.Increment();
Session["progress"] = "in progress";
Task.Factory.StartNew(DoIt);
}
public ActionResult StartTaskCompleted()
{
return Json(new {redirectUrl = Url.Action("TaskComplete", "First")});
}
private void DoIt()
{
try
{
// Long-running stuff takes place here, including updating
// Session["progress"].
}
finally
{
AsyncManager.OutstandingOperations.Decrement();
}
}
}
The same web page sets up a 2-second timer (via window.setInterval) to call the following controller (via JQuery $.getJSON) that has read-only access to the session...
[SessionState(SessionStateBehavior.ReadOnly)]
public class ProgressController: Controller
{
[HttpGet]
public ActionResult Current()
{
var data = Session["progress"];
return Json(data, JsonRequestBehavior.AllowGet);
}
}
The function invoked after getJSON returns updates a DIV to show the current progress. Ultimately the progress will be updated in the session by the 'long-running stuff takes place here' code, but as it is the call to ProgressController.Current() does not find any data in the session and always returns null.
I had assumed that JQuery AJAX requests sent from the same browser to the same IIS server would end up with the same session, but it seems not. Do I need to explicitly set the ASP.NET session key on these $.post and $.getJSON calls? Or is it just not possible to share session state between Controllers (even if one has R/O access) in this way (I can fall back to a slightly hacky solution with Application state and GUIDs).
That's normal behavior. Your StartTask action blocks until it completes. It doesn't return any response until the DoIt method has finished executing. I suppose that's why you are calling it with AJAX $.post. The problem is that while this method runs and writes to the session and all other requests from the same session will be queued by the ASP.NET engine. You cannot write and read from the session at the same time. You will have to find another thread-safe storage for the progress data other than the session.