Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fire and forget async method in asp.net mvc

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?

like image 868
acarlon Avatar asked Aug 29 '13 05:08

acarlon


People also ask

Is asynchronous fire and forget?

Fire-and-Forget is most effective with asynchronous communication channels, which do not require the Originator to wait until the message is delivered to the Recipient. Instead, the Originator can pursue other tasks as soon as the messaging system has accepted the message.

What is async in ASP NET MVC?

Async keyword is used to call the function/method as asynchronously. Await keyword is used when we need to get result of any function/method without blocking that function/method.

What is the use of async controllers in MVC?

The asynchronous controller enables you to write asynchronous action methods. It allows you to perform long running operation(s) without making the running thread idle. It does not mean it will take lesser time to complete the action.

How to return Task ActionResult?

The return type of Task<ActionResult> represents ongoing work and provides callers of the method with a handle through which to wait for the asynchronous operation's completion. In this case, the caller is the web service. Task<ActionResult> represents ongoing work with a result of ActionResult.


2 Answers

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()); 
like image 200
Stephen Cleary Avatar answered Sep 27 '22 16:09

Stephen Cleary


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

like image 33
Korayem Avatar answered Sep 27 '22 15:09

Korayem