Fire and forget async method in asp.net mvc

General answers, such as here and here , so as not to include or forget questions, do not use async / await, but instead use Task.Run or TaskFactory.StartNew , passing in a synchronous method.
However, sometimes the method I want to run and forget is asynchronous, and there is no equivalent synchronization method.

Update note / warning: As Stephen Cleary has shown, it is dangerous to continue working on a request after you send a response. The reason is that AppDomain may be closed while this work is still ongoing. For more information, see Link in his answer. In any case, I just wanted to point this out in advance so that I would not send anyone along the wrong path.

I think my case is valid because the actual work is done by another system (another computer on another server), so I only need to know that the message remains for this system. If there is an exception, the server or the user cannot do anything, and this does not affect the user, all I need to do is turn to the exception log and clear it manually (or implement some kind of automated mechanism). If AppDomain is turned off, I will have a residual file on the remote system, but I will choose this as part of my usual maintenance cycle, and since its existence is no longer known to my web server (database) and its name is uniquely timestamped, it will not cause any problems while he still lingers.

It would be ideal if I had access to the persistence mechanism, as Stephen Cleary pointed out, but, unfortunately, I was not at that time.

I thought I was just pretending that the DeleteFoo request completed normally on the client side (javascript), leaving the request open, but I need the information in the response to continue, so it will keep things up.

So the original question ...

eg:

 //External library public async Task DeleteFooAsync(); 

In my asp.net mvc code, I want to call DeleteFooAsync in fire-and-forget mode - I don't want to delay a response waiting for DeleteFooAsync to complete. If for some reason DeleteFooAsync fails (or throws an exception), the user or the program cannot do anything, so I just want to register an error.

Now I know that any exceptions will lead to inconspicuous exceptions, so the simplest case that 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 involved?

Another option I can think of is to create my own shell, for example:

 private void async DeleteFooWrapperAsync() { try { await DeleteFooAsync(); } catch(Exception exception ) { m_log.Error("DeleteFooAsync failed: " + exception.ToString()); } } 

and then call it using TaskFactory.StartNew (possibly completing the async action). However, this looks like a lot of shell code every time I want to call the async method in fire and forget mode.

My question is, is this the correct way to call the asynchronous method in fire-and-forget mode?

UPDATE:

Well, I found that the following is in my controller (not that the controller action should be asynchronous, because other pending asynchronous calls are expected):

 [AcceptVerbs( HttpVerbs.Post )] public async Task<JsonResult> DeleteItemAsync() { Task deleteTask = DeleteFooAsync(); ... } 

caused a form exception:

Unhandled exception: System.NullReferenceException: The object reference is not set to the object instance. in System.Web.ThreadContext.AssociateWithCurrentThread (BooleansetImpersonationContext)

It is discussed here and seems to be related to the SynchronizationContext, and "the returned task was transferred to the terminal state before all async work was completed."

So the only way that worked was:

 Task foo = Task.Run( () => DeleteFooAsync() ); 

My understanding of why this works is that StartNew gets a new thread for DeleteFooAsync to work.

Unfortunately, Scott's suggestion below does not work to handle exceptions in this case, since foo is no longer the DeleteFooAsync task, but rather the Task.Run task, and therefore does not handle exceptions from DeleteFooAsync. My UnobservedTaskException is ultimately thrown, so at least it still works.

So, I guess the question still stands, how do you shoot and forget the async method in asp.net mvc?

+44
c # asynchronous async-await
Aug 29 '13 at 5:12
source share
3 answers

First of all, let me point out that β€œfire and forget” is almost always a bug in ASP.NET applications. "Fire and forget" is only an acceptable approach if you don't care if DeleteFooAsync .

If you agree to accept this limitation, I have some code in my blog that will post tasks during ASP.NET execution, and it accepts both synchronous and asynchronous work.

You can write a one-time wrapper method to register exceptions per se:

 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 save TaskScheduler.UnobservedTaskException and just TaskScheduler.UnobservedTaskException it like this:

 BackgroundTaskManager.Run(() => DeleteFooAsync()); 
+40
Aug 29 '13 at 11:14
source share

Starting with .NET 4.5.2 you can do the following

 HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => await LongMethodAsync()); 

But it only works in an ASP.NET domain

The HostingEnvironment.QueueBackgroundWorkItem method allows you to schedule small background jobs. ASP.NET keeps track of these items and prevents IIS from abruptly terminating the workflow until all background work items are complete. This method cannot be called outside the domain of a managed ASP.NET application.

More details here: https://msdn.microsoft.com/en-us/library/ms171868(v=vs.110).aspx#v452

+12
Apr 20 '16 at 16:11
source share

The best way to handle this is to use the ContinueWith method and pass OnlyOnFaulted .

 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 placed my message box, you would place your registrar.

+7
Aug 29 '13 at 5:29
source share



All Articles