In asp .net MVC project I'm trying to call controller's Get method with some long lasting operation asynchronously. The problem is that after first call other incoming requests are waiting when this operation will be finished. And after that they are handled one by one in order they were requested.
Tested on code below.
So if for example I make requests to SomeGetMethod multiple times, for the incoming request this method will be executed only when SomeGetMethod finished for previous call.
public class SomeController : Controller
{
public ActionResult Index()
{
return View();
}
public async Task<ActionResult> SomeGetMethod(int id)
{
await TestAsync();
return Content("1");
}
public async Task<int> TestAsync()
{
await Task.Run(() => Thread.Sleep(20000));
return 1;
}
}
What might be the reason of that?
Thanks in advance!
Task.Run could be executed on the same thread as your controller, so you should change Thread.Sleep with Task.Delay.
No that not the case. Its been pretending like this because both of the API's have same level of thread.sleep so its obvious that it will perform FIFO behavior i.e API hits first will response first
To verify this kindly set Thread.Sleep(1) for the second API during debugging and you will find out that second API will response before the response of first API.
You can also verify by placing a debugger and you will find out that second API will hit server as soon as you send request.
You are experiencing Threadpool starvation. You start off with relatively few threads in your pool that can service requests. What you are doing is blocking a threadpool thread for 20 seconds, which means it is doing nothing for that period. When you hit that endpoint over and over, you will run into a situation where all your threads that can service requests are tied up, doing nothing but waiting.
To fix this, don't offload to a threadpool thread i.e. don't do Task.Run() and change to await Task.Delay(20000) for optimal throughput.
You should do a more correct test, so only one of the methods is waiting.
public class SomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult NoWait()
{
return Content("1");
}
public async Task<int> SleepWait()
{
await Task.Run(() => Thread.Sleep(20000));
return 2;
}
}
Then call SleepWait() and right after NoWait(), what is the output then?
Related
I have a process I would like to run in the background. This is executed with a click of an action link.
Action to call:
public async Task<ActionResult> ProcessRec()
{
await Task.Run(() => waitTimer());
return RedirectToAction("Index", "Home");
}
public void waitTimer()
{
Thread.Sleep(10000);
}
This however waits for the full 10 seconds before redirecting me to my "Index, Home" action. I am very new to Await/Async so I know I am interpreting something wrong here. How do I get the application to return to this action, while the waitTimer is executing in the background? Thanks!!
await, as you found out, blocks the response from returning to the user before it is done. Normally you would just put your background work on another thread and set it to "fire and forget" by not awaiting, however in ASP.NET IIS will shut down AppDomains that are not being used and Task.Run does not inform IIS that your background thread "is using the AppDomain" so your background thread could be terminated with a Thread.Abort() during an AppDomain shutdown.
If you are using .NET 4.5.2 or newer you can tell IIS you have a background worker that you need to be kept alive via QueueBackgroundWorkItem. You would use it like this
public ActionResult ProcessRec()
{
HostingEnvironment.QueueBackgroundWorkItem(waitTimer);
return RedirectToAction("Index", "Home");
}
public void waitTimer(CancellationToken token)
{
Thread.Sleep(10000);
}
//You also could do
public async Task waitTimer2(CancellationToken token)
{
await Task.Delay(10000);
}
Now this does not guarantee that IIS will not shut down your app domain but it does let it know you are in the middle of something and asks for more time when it does try to shut it down (You get up to 90 additional seconds after a shutdown is started to complete all queued background items by default).
For more information read this MSDN blog introducing it.
This however waits for the full 10 seconds before redirecting me to my "Index, Home" action.
Right, that's because await asynchronously waits for the operations completion. It will yield the thread back to the pool until the operation completes.
How do I get the application to return to this action, while the waitTimer is executing in the background?
Task.Run is dangerous in the fact it doesn't register work with IIS which can lead to problems. Instead, you can use BackgroundTaskManager or HangFire which register it's execution with ASP.NET:
BackgroundTaskManager.Run(() => { waitTimer() };
return RedirectToAction("Index", "Home");
I'm thinking about sending a message to a queue (like azure storage/service bus queue) so that you can get your response immediately.
And then create another service to dequeue and process the message (execute your long running task)
Also if this is an azure website (web app), you can use the web job!
Hope that helps.
public async Task<IActionResult> Index()
{
return await Task.Run(() =>
{
return View();
});
}
From my understanding when we use await and the awaited task is not yet completed then the execution returns to caller. It works fine in server side(calling the async method from server side method itself). But what happends when I call from UI to an async method.
public class TestController : ApiController
{
IList<string> lstString = new List<string>();
public async Task<IList<string>> GetMyCollectionAsync()
{
lstString.Add("First");
string secString = await GetSecondString(); // I am expecting a response back to UI from here.
lstString.Add(secString);
lstString.Add("Third");
return lstString;
}
private async Task<string> GetSecondString()
{
await Task.Delay(5000);
return "Second after await";
}
}
I tested with the above API from browser like
http://localhost:port/Test
, but I got response only after 5 sec in my UI.
Am I thinking it wrongly?
Am I thinking it wrongly?
Yes. async-await does not change the nature of the HTTP protocol, which is of type request-response. When using async-await inside an ASP.NET controller, using an async method will not yield a response to the caller, it would only yield the requests thread back to the threadpool.
But if this is true, then using async method having a single await in controller side is not useful. right? because it took the same time of synchronous call
Async shines when you need scalability. It isn't about "yield this response as fast as possible", it's about being able to handle a large amount of requests without exhausting the thread-pool. Once the thread starts executing IO work, instead of being blocked, it is returned. Thus, able to serve more requests.
Async by itself does not make anything "go faster", which is a conception I see people thinking alot. If you're not going to be hitting your web service with many concurrent requests, you're most likely not going to be seeing any benefit from using it. As #Scott points out, an async method has a slight overhead as it generates a state machine behind the scenes.
async/await allow the thread to go off servicing other requests while there is that idle 5 seconds, but ultimately everything in GetMyCollectionAsync has to complete before a response is sent to the client.
So I'd expect your code to take 5 seconds and return all 3 strings in the response.
I have a process I would like to run in the background. This is executed with a click of an action link.
Action to call:
public async Task<ActionResult> ProcessRec()
{
await Task.Run(() => waitTimer());
return RedirectToAction("Index", "Home");
}
public void waitTimer()
{
Thread.Sleep(10000);
}
This however waits for the full 10 seconds before redirecting me to my "Index, Home" action. I am very new to Await/Async so I know I am interpreting something wrong here. How do I get the application to return to this action, while the waitTimer is executing in the background? Thanks!!
await, as you found out, blocks the response from returning to the user before it is done. Normally you would just put your background work on another thread and set it to "fire and forget" by not awaiting, however in ASP.NET IIS will shut down AppDomains that are not being used and Task.Run does not inform IIS that your background thread "is using the AppDomain" so your background thread could be terminated with a Thread.Abort() during an AppDomain shutdown.
If you are using .NET 4.5.2 or newer you can tell IIS you have a background worker that you need to be kept alive via QueueBackgroundWorkItem. You would use it like this
public ActionResult ProcessRec()
{
HostingEnvironment.QueueBackgroundWorkItem(waitTimer);
return RedirectToAction("Index", "Home");
}
public void waitTimer(CancellationToken token)
{
Thread.Sleep(10000);
}
//You also could do
public async Task waitTimer2(CancellationToken token)
{
await Task.Delay(10000);
}
Now this does not guarantee that IIS will not shut down your app domain but it does let it know you are in the middle of something and asks for more time when it does try to shut it down (You get up to 90 additional seconds after a shutdown is started to complete all queued background items by default).
For more information read this MSDN blog introducing it.
This however waits for the full 10 seconds before redirecting me to my "Index, Home" action.
Right, that's because await asynchronously waits for the operations completion. It will yield the thread back to the pool until the operation completes.
How do I get the application to return to this action, while the waitTimer is executing in the background?
Task.Run is dangerous in the fact it doesn't register work with IIS which can lead to problems. Instead, you can use BackgroundTaskManager or HangFire which register it's execution with ASP.NET:
BackgroundTaskManager.Run(() => { waitTimer() };
return RedirectToAction("Index", "Home");
I'm thinking about sending a message to a queue (like azure storage/service bus queue) so that you can get your response immediately.
And then create another service to dequeue and process the message (execute your long running task)
Also if this is an azure website (web app), you can use the web job!
Hope that helps.
public async Task<IActionResult> Index()
{
return await Task.Run(() =>
{
return View();
});
}
I had a situation recently where I had an ASP.NET WebAPI controller that needed to perform two web requests to another REST service inside its action method. I had written my code to have functionality separated cleanly into separate methods, which looked a little like this example:
public class FooController : ApiController
{
public IHttpActionResult Post(string value)
{
var results = PerformWebRequests();
// Do something else here...
}
private IEnumerable<string> PerformWebRequests()
{
var result1 = PerformWebRequest("service1/api/foo");
var result = PerformWebRequest("service2/api/foo");
return new string[] { result1, result2 };
}
private string PerformWebRequest(string api)
{
using (HttpClient client = new HttpClient())
{
// Call other web API and return value here...
}
}
}
Because I was using HttpClient all web requests had to be async. I've never used async/await before so I started naively adding in the keywords. First I added the async keyword to the PerformWebRequest(string api) method but then the caller complained that the PerformWebRequests() method has to be async too in order to use await. So I made that async but now the caller of that method must be async too, and so on.
What I want to know is how far down the rabbit hole must everything be marked async to just work? Surely there would come a point where something has to run synchronously, in which case how is that handled safely? I've already read that calling Task.Result is a bad idea because it could cause deadlocks.
What I want to know is how far down the rabbit hole must everything be
marked async to just work? Surely there would come a point where
something has to run synchronously
No, there shouldn't be a point where anything runs synchronously, and that is what async is all about. The phrase "async all the way" actually means all the way up the call stack.
When you process a message asynchronously, you're letting your message loop process requests while your truly asynchronous method runs, because when you go deep down the rabit hole, There is no Thread.
For example, when you have an async button click event handler:
private async void Button_Click(object sender, RoutedEventArgs e)
{
await DoWorkAsync();
// Do more stuff here
}
private Task DoWorkAsync()
{
return Task.Delay(2000); // Fake work.
}
When the button is clicked, runs synchronously until hitting the first await. Once hit, the method will yield control back to the caller, which means the button event handler will free the UI thread, which will free the message loop to process more requests in the meanwhile.
The same goes for your use of HttpClient. For example, when you have:
public async Task<IHttpActionResult> Post(string value)
{
var results = await PerformWebRequests();
// Do something else here...
}
private async Task<IEnumerable<string>> PerformWebRequests()
{
var result1 = await PerformWebRequestAsync("service1/api/foo");
var result = await PerformWebRequestAsync("service2/api/foo");
return new string[] { result1, result2 };
}
private async string PerformWebRequestAsync(string api)
{
using (HttpClient client = new HttpClient())
{
await client.GetAsync(api);
}
// More work..
}
See how the async keyword went up all the way to the main method processing the POST request. That way, while the async http request is handled by the network device driver, your thread returns to the ASP.NET ThreadPool and is free to process more requests in the meanwhile.
A Console Application is a special case, since when the Main method terminates, unless you spin a new foreground thread, the app will terminate. There, you have to make sure that if the only call is an async call, you'll have to explicitly use Task.Wait or Task.Result. But in that case the default SynchronizationContext is the ThreadPoolSynchronizationContext, where there isn't a chance to cause a deadlock.
To conclude, async methods shouldn't be processed synchronously at the top of the stack, unless there is an exotic use case (such as a Console App), they should flow asynchronously all the way allowing the thread to be freed when possible.
You need to "async all the way up" to the very top of the call stack, where you reach a message loop that can process all of the asynchronous requests.
How do i get my MVCcontroller method to call a long running WCF task asynchronously and redirect immediately after the asynchronously call is fired?
I have configured my WCF service reference to "Generate asynchronously operations" yet when the method is called i can see in the debugger that code steps through and passes the "RedirectToAction("RedirectToActionTestCompleted")" line but the browser does not redirect until the WCF task is finished.
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
public ActionResult RedirectToActionTest()
{
Service1Client client = new Service1Client();
client.TestWcfCallAsync();
return RedirectToAction("RedirectToActionTestCompleted");
}
public ActionResult RedirectToActionTestCompleted()
{
return View();
}
}
And the WCF service method
public void TestWcfCall()
{
Thread.Sleep(30000); //30 seconds
}
Why is the web page waiting for the WCF method to complete?
The whole point of asynchronous methods is that they should not block. Your test method is not asynchronous.
Use a true async WCF call and a async controller = nothing will block.
Implement from AsyncController instead of Controller.
http://msdn.microsoft.com/en-us/library/ee728598.aspx
Use a workflow solution like WF. The page can just start the workflow and return. The execution of the WCF task can then be managed by the workflow.
If you want to redirect immediately you're not interested in WCF call result in the current http request, right? If that's true you may want to use OneWay operation call. It will still block and if you really don't want to block even on OneWay calls you can start it asyncronously - fire-and-forget scenario.