I read a lot of codes trying to use Task.Run without success.
What I want to achive:
In an ASP.NET WebForm event (click event handler) call a Fire & Forget method (not block the current flow of execution).
What I tried and don't understant why it's not working:
First Version:
protected void btn_Click(object sender, EventArgs e)
{
// Some actions
// Should insert a record in database --> OK
//Tried this call with and without ConfigureAwait(false)
Task.Run(() => MyClass.doWork()).ConfigureAwait(false);
// Should insert a record in database --> OK
// Some actions not blocked by the previous call
}
public static class MyClass
{
public static void doWork()
{
// Should insert a record in database --> NOT INSERTED
}
}
Second Version:
protected void btn_Click(object sender, EventArgs e)
{
// Some actions
// Should insert a record in database --> OK
Bridge.call_doWork();
// Should insert a record in database --> OK
// Some actions not blocked by the previous call
}
public static class Bridge
{
public static async Task call_doWork()
{
//Tried this call with and without ConfigureAwait(false)
await Task.Run(() => MyClass.doWork()).ConfigureAwait(false);
}
}
public static class MyClass
{
public static void doWork()
{
// Should insert a record in database --> NOT INSERTED
}
}
So I call the Fire & Forget method, which should insert a record in the database, but there's no record inserted.
The inserts before and after the call to the Fire & Forget method are done.
I don't know how to resolve my issue.
HttpContext will not be available in threads other than the main thread, so you can't depend on it.
But you can pass data from HttpContext to your method when you start the task. For example:
Task.Run(() => MyClass.doWork(HttpContext.Current.Session["somedata"])).ConfigureAwait(false);
Why didn't I get an EventViewer event with the HttpContext call? I got one when using HostingEnvironment.QueueBackgroundWorkItem instead of Task.Run.
OK, first off, if you have QueueBackgroundWorkItem available, why would you ever use Task.Run for fire-and-forget work?
As I describe on my blog, using Task.Run for fire-and-forget on ASP.NET is a really bad idea! QueueBackgroundWorkItem is the minimum viable solution, and that's only if you accept unreliability.
QueueBackgroundWorkItem does a couple of things for you beyond Task.Run: it registers the work with the ASP.NET runtime (which minimizes but does not eliminate the possibility that the work will not complete), and it catches exceptions and logs them for you (which is why you were seeing the event notifications).
So, you were seeing an event for your exception because QBWI was doing that for you. Whereas with the Task.Run code, the exception would be caught and placed on the returned Task (as it should be), and then your code completely ignored that task, thus silently swallowing the exception.
Yes! I use HttpContext to get the Session. But I can't change that without changing a lot of code.
As others have noted, HttpContext is only valid within a request context. So, when you explicitly run background code, of course it doesn't have a request context. Background code must be independent of requests, by definition.
But there's one other really important consideration that I think you're overlooking:
public static void doWork()
{
// Should insert a record in database --> NOT INSERTED
}
Are you sure that your app is perfectly OK if doWork once in a while doesn't execute? Because that's what happens to background tasks. ASP.NET was designed to respond to requests, not run background tasks.
So, every once in a blue moon, the record that should have been inserted by doWork won't show up. If this is unacceptable, then you shouldn't be doing fire-and-forget.
Related
I have a Winform project that inside of the winform class I have a property called DataBindingTasks like so.
// create a task list to determine when tasks have finished during load
protected List<Task> DataBindingTasks = new List<Task>();
I have several async void methods that I am calling in the winform "Load" event that are all similar to the following.
private async void BindSomething(int millSecToWait)
{
var someTask = Task.Factory.StartNew(() =>
{
// do some work
System.Threading.Thread.Sleep(millSecToWait);
// return some list for binding
return new List<int>();
});
// add the task to the task list
DataBindingTasks.Add(someTask);
// wait until data has loaded
var listToBind = await someTask;
// bind the data to a grid
}
I am calling the BindSomething methods on load.
I say methods because there are several of these binding types of methods that are called on load.
private void Form_Load(object sender, EventArgs e)
{
// async bind something and let UI continue
// fire and forget
BindSomething(5000);
BindSomething(8000);
BindSomething(2000);
BindSomething(2000);
// code to execute when all data binding tasks have completed
Task.WhenAll(DataBindingTasks).ContinueWith((x) =>
{
// Do something after all async binding tasks have completed
});
}
EXCEPT the ContinueWith code is executing even though all the tasks have not completed.
Here is a screen shot showing that all task are not complete.
UPDATED 10/29
The problem is obviously deeper than the sample code above and the sample code above does not fully explain the true scenario.
I will try to explain in greater detail but try to not make it to long.
This is a Winform application.
We have created a base winform "BaseForm" that all other winforms will inherit from.
We have overridden the "OnLoad" event in the "BaseForm" so that we can call a new method that all inherited forms will have called "LoadData".
Since "LoadData" can have async method calls, the base form needs to know when the "LoadData" method is finished.
So in the base form was have some of the following:
protected List<Task> DataBindingTasks = new List<Task>();
public event EventHandler DataBindingTasksComplete;
protected void OnDataBindingTasksComplete(EventArgs e)
{
if (DataBindingTasksComplete != null)
{
DataBindingTasksComplete(this, e);
}
// now clear the list
DataBindingTasks.Clear();
}
// NOTE: this is inside the OnLoad called before base.OnLoad(e)
Task.WhenAll(DataBindingTasks).ContinueWith((x) =>
{
OnDataBindingTasksComplete(EventArgs.Empty);
});
The hope was that all inherited forms would add any of their "async" tasks to this list so that the base form could fire the "DataBindingTasksComplete" event so they would know that form has finished loading.
The problem "as perceived to us at the time of the issue" was that the "WhenAll().ContinueWith" was not waiting until all the tasks on the list had completed.
BUT as someone noted, the list might have changed.
So here is most likely what happened.
There are 4 "BindSomething" methods that are marked async all called from the Form_Load
The 2nd or so line down inside the "BindSomething" method is used to add a task to the "BaseForm.DataBindingTasks" list.
Since each of these calls are marked async, the Form_Load continues to call all 4 as a "fire and forget".
After that, it returns back to the BaseForm OnLoad which then looks at the "DataBindingTasks" list to see if all tasks have completed.
My best guess is that one of the "BindSomething" methods was in the middle of adding its task to the list yet the Base.OnLoad has already started looking at the list.
I could add 4 "fake" tasks (like thread sleep) to the list even before calling the "BindSomething" methods as "place holders" and then inside the "BindSomething" methods swap out the "fake" tasks with the "real" tasks.
This seams messy and most likely will cause other issues.
The most likely fix is to not use a task list / WhenAll.ContinueWith and instead call the load data with "await" and then raise the event on the next line.
The async void methods are called as fire-and-forget, and there is no way to wait for them, that's why your delegate don't wait properly - it simply can't do that. So you need some changes in your code.
Update: #Servy noted the main problem in your code which I've missed, thanks for him:
DataBindingTasks.Add(someTask);
This operation isn't thread-safe! You simply losing some of your tasks during parallel calls for Add method. You need to change this: by using lock, by using ConcurrentCollection or by using data separation: assign a task to array by different indexes so parallel tasks aren't intersect each other.
First of all, you shouldn't use the StartNew in this case, use the Task.Run, otherwise you can met some problems in your app.
Second thing is that you can make the Load method async and await it, so your UI wouldn't freeze, and you can switch the signature for your BindSomething methods to became awaitable, as #digimunk mentioned:
// note that we return the task here
private async Task BindSomething(int millSecToWait)
{
// use Task.Run in this case
var someTask = Task.Run(() =>
{
// Some work
System.Threading.Thread.Sleep(millSecToWait);
// return some list for binding
return new List<int>();
});
DataBindingTasks.Add(someTask);
// wait until data has loaded
var listToBind = await someTask;
// bind the data to a grid
}
// async void for the event handler
private async void Load()
{
// start tasks in fire-and-forget fashion
BindSomething(5000);
BindSomething(8000);
BindSomething(2000);
// code to execute when all data binding tasks have completed
await Task.WhenAll(DataBindingTasks);
// Do something after all binding is complete
}
In this case you can await the Load method safely.
You don't need .ContinueWith(). Just await the Task.WhenAll(), and then put whatever code you want to run after it under it. Also, change the "void" in the method signature to "async Task".
Recently I've finished designing a PagedDataGridView custom control, it works very well, but now I'd like to improve its performance.
How?, well, I've seen some UI blocking when showing new pages.
In short words, this:
public class PagedDataGridView() : UserControl
{
Paging paging = new Paging();
public PagedDataGridView()
{
paging.LoadDataClientMethod = LoadDataOnGrid;
}
private void LoadDataOnGrid()
{
// Some heavy set data source here, using functions from 'paging' object
}
}
What I'm trying to do (using the async / await pattern):
That async method DoPaging pauses until the await 'LoadDataOnGrid' is complete, that way the UI thread is not blocked, be asynchronous.
// Class that handles paging methods, variables,
// also offers navigation controls, such as buttons, labels, etc.
internal class Paging
{
// Represents the method that code client uses to load its own data
public Action LoadDataClientMethod;
// HERE:
private async Task DoPaging()
{
// some calculations
if (LoadDataClientMethod != null)
{
// I realizad that calling Run method, runs it out of context
// How to run this method, and update the UI
await Task.Run(() => LoadDataClientMethod());
}
// Update controls, showing current page, etc
UpdateUI();
}
// Navigation buttons
private void btnGoNextPage(object sender, EventArgs e)
{
// go next page calculations
// Then how to call the async method
DoPaging(); // -> doing this, VS shows a warning:
/* Because this call is not awaited, the current method
continues to run before the call is completed */
}
}
I'm just starting to learn about async - await coding, any correction or advice will be greatly appreciated, thanks.
There is a big difference between:
private void btnGoNextPage(object sender, EventArgs e)
{
DoPaging();
}
and
private async void btnGoNextPage(object sender, EventArgs e)
{
await DoPaging();
}
Exception handling. If the former throws an exception, two things might happen:
If you're using .NET 4.0, the swallowed task will be re-thrown from the Finalizer thread and will cause your application to crash
If you're using .NET 4.5, the task will be swallowed and will go un-noticed and will not be re-thrown at all, thus possibly entering your application in a corrupted state which you wont be aware of.
in the latter example, the exception will propogate to the await point, and you can handle it gracefully by adding a try-catch block.
As a side note, i asked you in the comments what kind of work is being done that is blocking your UI thread, and you said that you are making a call to your database to retrieve data.
Work being done against a database is IO bound work, and most providers expose async endpoints to access data, such as Entity Framework, ADO.NET, etc. You can make use of that naturally async behavior by not using any threadpool threads to do the work for you (with Task.Run as you're doing in your example). You can do that when you go "async all the way", and your database query can be used with the await keyword. That way, while the query is retrieving the data, the thread that invoked the query (in your example, the UI thread) is freed and can do more work, thus your UI will stay responsive.
I suggest you look into that and see if your database provider has those async endpoints.
Just add async to the button click event handler method and await the call to DoPaging():
private async void btnGoNextPage(object sender, EventArgs e)
{
await DoPaging();
}
The difference between doing it like this rather than the way you had that gives the warning (and is in fact why the warning is given) is that if you added any code after the call to DoPaging() in the event handler it would now occur after the task has complete, whereas before it would execute immediately after the call.
The general answers such as here and here to fire-and-forget questions is not to use async/await, but to use Task.Run or TaskFactory.StartNew passing in the synchronous method instead. However, sometimes the method that I want to fire-and-forget is async and there is no equivalent sync method.
Update Note/Warning: As Stephen Cleary pointed out below, it is dangerous to continue working on a request after you have sent the response. The reason is because the AppDomain may be shut down while that work is still in progress. See the link in his response for more information. Anyways, I just wanted to point that out upfront, so that I don't send anyone down the wrong path.
I think my case is valid because the actual work is done by a different system (different computer on a different server) so I only need to know that the message has left for that system. If there is an exception there is nothing that the server or user can do about it and it does not affect the user, all I need to do is refer to the exception log and clean up manually (or implement some automated mechanism). If the AppDomain is shut down I will have a residual file in a remote system, but I will pick that up as part of my usual maintenance cycle and since its existence is no longer known by my web server (database) and its name is uniquely timestamped, it will not cause any issues while it still lingers.
It would be ideal if I had access to a persistence mechanism as Stephen Cleary pointed out, but unfortunately I don't at this time.
I considered just pretending that the DeleteFoo request has completed fine on the client side (javascript) while keeping the request open, but I need information in the response to continue, so it would hold things up.
So, the original question...
for example:
//External library
public async Task DeleteFooAsync();
In my asp.net mvc code I want to call DeleteFooAsync in a fire-and-forget fashion - I don't want to hold up the response waiting for DeleteFooAsync to complete. If DeleteFooAsync fails (or throws an exception) for some reason, there is nothing that the user or the program can do about it so I just want to log an error.
Now, I know that any exceptions will result in unobserved exceptions, so the simplest case I can think of is:
//In my code
Task deleteTask = DeleteFooAsync()
//In my App_Start
TaskScheduler.UnobservedTaskException += ( sender, e ) =>
{
m_log.Debug( "Unobserved exception! This exception would have been unobserved: {0}", e.Exception );
e.SetObserved();
};
Are there any risks in doing this?
The other option that I can think of is to make my own wrapper such as:
private void async DeleteFooWrapperAsync()
{
try
{
await DeleteFooAsync();
}
catch(Exception exception )
{
m_log.Error("DeleteFooAsync failed: " + exception.ToString());
}
}
and then call that with TaskFactory.StartNew (probably wrapping in an async action). However this seems like a lot of wrapper code each time I want to call an async method in a fire-and-forget fashion.
My question is, what it the correct way to call an async method in a fire-and-forget fashion?
UPDATE:
Well, I found that the following in my controller (not that the controller action needs to be async because there are other async calls that are awaited):
[AcceptVerbs( HttpVerbs.Post )]
public async Task<JsonResult> DeleteItemAsync()
{
Task deleteTask = DeleteFooAsync();
...
}
caused an exception of the form:
Unhandled Exception: System.NullReferenceException: Object reference
not set to an instance of an object. at System.Web.ThreadContext.AssociateWithCurrentThread(BooleansetImpersonationContext)
This is discussed here and seems to be to do with the SynchronizationContext and 'the returned Task was transitioned to a terminal state before all async work completed'.
So, the only method that worked was:
Task foo = Task.Run( () => DeleteFooAsync() );
My understanding of why this works is because StartNew gets a new thread for DeleteFooAsync to work on.
Sadly, Scott's suggestion below does not work for handling exceptions in this case, because foo is not a DeleteFooAsync task anymore, but rather the task from Task.Run, so does not handle the exceptions from DeleteFooAsync. My UnobservedTaskException does eventually get called, so at least that still works.
So, I guess the question still stands, how do you do fire-and-forget an async method in asp.net mvc?
First off, let me point out that "fire and forget" is almost always a mistake in ASP.NET applications. "Fire and forget" is only an acceptable approach if you don't care whether DeleteFooAsync actually completes.
If you're willing to accept that limitation, I have some code on my blog that will register tasks with the ASP.NET runtime, and it accepts both synchronous and asynchronous work.
You can write a one-time wrapper method for logging exceptions as such:
private async Task LogExceptionsAsync(Func<Task> code)
{
try
{
await code();
}
catch(Exception exception)
{
m_log.Error("Call failed: " + exception.ToString());
}
}
And then use the BackgroundTaskManager from my blog as such:
BackgroundTaskManager.Run(() => LogExceptionsAsync(() => DeleteFooAsync()));
Alternatively, you can keep TaskScheduler.UnobservedTaskException and just call it like this:
BackgroundTaskManager.Run(() => DeleteFooAsync());
As of .NET 4.5.2, you can do the following
HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => await LongMethodAsync());
But it only works within ASP.NET domain
The HostingEnvironment.QueueBackgroundWorkItem method lets you
schedule small background work items. ASP.NET tracks these items and
prevents IIS from abruptly terminating the worker process until all
background work items have completed. This method can't be called
outside an ASP.NET managed app domain.
More here: https://msdn.microsoft.com/en-us/library/ms171868(v=vs.110).aspx#v452
The best way to handle it is use the ContinueWith method and pass in the OnlyOnFaulted option.
private void button1_Click(object sender, EventArgs e)
{
var deleteFooTask = DeleteFooAsync();
deleteFooTask.ContinueWith(ErrorHandeler, TaskContinuationOptions.OnlyOnFaulted);
}
private void ErrorHandeler(Task obj)
{
MessageBox.Show(String.Format("Exception happened in the background of DeleteFooAsync.\n{0}", obj.Exception));
}
public async Task DeleteFooAsync()
{
await Task.Delay(5000);
throw new Exception("Oops");
}
Where I put my message box you would put your logger.
I have some code that works as follows:
App.xaml calls a SetUp() method which populates a local collection using async calls and exposes the collection as a public property.
That's all good.
Now I make an instance of the first page in my Silverlight app like so
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Summary();
}
In the constructor of Summary() I expect those async calls to have been complete and my collection to be filled but the async calls have not yet complete. Even if I do a Thread.Sleep(100000....) before i make an instance on Summary() this is the case
And the thing is that until the constructor of Summary() is exited and the UI displayed to the user my async calls do not get kicked off!
What!!!
Is there anything I can do about that or is that just the way asyn calls work i.e. they wait until the current until of work finished before firing?
This is how I work round this situation (I'll use simple string download as an example):-
private void Application_Startup(object sender, StartupEventArgs e)
{
WebClient web = new WebClient();
web.DownloadStringCompleted += (s, args) =>
{
// Do stuff with args.Result);
this.RootVisual = new Summary();
};
web.DownloadStringAsync(new Uri("SomeUrl.txt", UriKind.Relative));
}
Note that the construction of Summary and the assignment to RootVisual are defered until the asynchronous download is complete.
Silverlight was specifically designed to not have any blocking operations -- that's why you have to do async in the first place. What you really have to do is make your app run properly while waiting for the async completion handler to execute.
This is by design. The alternative would be that the user has to wait longer before he sees anything at all.
If you really want to prevent showing an incomplete summery then first shown another page with 'waiting' or a progress bar. Fire the async events from that page or its parent. Then show the Summary when the async call returns.
To deal with an incomplete Summary you might want to use ICommand
I know this is an old thread, but for all following reader i want to provide my experience with a similar problem. If i understood you correctly - the reason why your async calls do not complete is because you block the main thread. I ran into the same problem, if you block the main thread none of the other threads continue. The solution was to do the async calls inside a backgroundworker and show, like pauldendulk said, a waiting - page. Use Debug.WriteLine() to monitor the process in the output.
I have an asynchronous operation that for various reasons needs to be triggered using an HTTP call to an ASP.NET web page. When my page is requested, it should start this operation and immediately return an acknowledgment to the client.
This method is also exposed via a WCF web service, and it works perfectly.
On my first attempt, an exception was thrown, telling me:
Asynchronous operations are not allowed in this context.
Page starting an asynchronous operation has to have the Async
attribute set to true and an asynchronous operation can only be
started on a page prior to PreRenderComplete event.
So of course I added the Async="true" parameter to the #Page directive. Now, I'm not getting an error, but the page is blocking until the Asynchronous operation completes.
How do I get a true fire-and-forget page working?
Edit: Some code for more info. It's a bit more complicated than this, but I've tried to get the general idea in there.
public partial class SendMessagePage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string message = Request.QueryString["Message"];
string clientId = Request.QueryString["ClientId"];
AsyncMessageSender sender = new AsyncMessageSender(clientId, message);
sender.Start();
Response.Write("Success");
}
}
The AsyncMessageSender class:
public class AsyncMessageSender
{
private BackgroundWorker backgroundWorker;
private string client;
private string msg;
public AsyncMessageSender(string clientId, string message)
{
this.client = clientId;
this.msg = message;
// setup background thread to listen
backgroundThread = new BackgroundWorker();
backgroundThread.WorkerSupportsCancellation = true;
backgroundThread.DoWork += new DoWorkEventHandler(backgroundThread_DoWork);
}
public void Start()
{
backgroundThread.RunWorkerAsync();
}
...
// after that it's pretty predictable
}
If you are running webforms set Ansync = "true" in your .aspx page where you are making the request. <%# Page Language="C#" Async="true" ... %>
If you don't care about returning anything to the user, you can just fire up either a separate thread, or for a quick and dirty approach, use a delegate and invoke it asynchrnously. If you don't care about notifying the user when the async task finishes, you can ignore the callback. Try putting a breakpoint at the end of the SomeVeryLongAction() method, and you'll see that it finishes running after the page has already been served up:
private delegate void DoStuff(); //delegate for the action
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
//create the delegate
DoStuff myAction = new DoStuff(SomeVeryLongAction);
//invoke it asynchrnously, control passes to next statement
myAction.BeginInvoke(null, null);
Button1.Text = DateTime.Now.ToString();
}
private void SomeVeryLongAction()
{
for (int i = 0; i < 100; i++)
{
//simulation of some VERY long job
System.Threading.Thread.Sleep(100);
}
}
OK, here's the problem: the Async attribute is for the case where your page is going to call some long-running task that also blocks the thread, and then your page needs the output from that task in order to return info to the user. For example, if your page needed to call a web service, wait for its response, and then use the data from the response to render your page.
The reason you'd use the Async attribute is to avoid blocking the thread. This is important because ASP.NET applications use a thread pool to serve requests, and there are only a relatively small number of threads available. And if each call ties up the thread while waiting on the web service call, then soon you're going to hit enough concurrent users that users are going to have to wait until these web service calls complete. The Async attribute lets the thread return to the thread pool and serve other concurrent visitors to your web site, rather than forcing it to sit still doing nothing while waiting for the web service call to return.
The upshot for you is this: the Async attribute is designed for the case where you can't render the page until the asynchronous task completes, and that's why it doesn't render the page immediately.
You need to launch your own thread, and make it a daemon thread. I don't remember the exact syntax for that, but you can easily find it in the doc by searching the BCL doc for "daemon". This means the thread will keep your application from shutting down while it is alive, which is important because ASP.NET and IIS reserve the right to "recycle your process" when they deem it necessary, and if that happens while your thread is working, your task will be stopped. Making the thread daemon will prevent this (except for some possible rare edge cases ... you'll find out more when you find the documentation on this).
That daemon thread is where you will kick off these tasks. And after you've told the daemon thread to do the task, you can immediately render your page ... so the rendering of the page will happen immediately.
Even better than a daemon thread in your ASP.NET process, though, would be to implement a Windows Service for doing the task. Have your ASP.NET application communicate the task to be performed to the Service. No need for a daemon thread and no need to worry about your ASP.NET process being recycled. How do you tell the Service to do the task? Perhaps through WCF, or perhaps by inserting a record into a database table that the Service polls. Or a number of other ways.
EDIT: Here's another idea, which I have used before for this very same purpose. Write the info about your task into an MSMQ queue. Have another process (maybe even on another machine) pull from that queue and do the time-consuming task. The job of inserting into a Queue is optimized to return as quickly as possible, so your thread won't block while the data you put in the Queue is sent across the wire or anything like that. It is one of the fastest ways to make note of the fact that a task needs to be done without waiting for that task to execute.
You can work around this limitation quite easily and without even setting Async to true.
public void Start()
{
new Task(() =>
{
backgroundThread.RunWorkerAsync();
}).Start();
}
If you get this error when calling web service asynchronously, make sure adding the Async='true' attribute as instructed by the
exception message?
top of the page < Page Language='VB' Async='true' AutoEventWireup='false'
CodeFile='mynewpage.aspx.vb' Inherits='mynewpage' %>