I have a Controller Action method to Save user Details like below.
public async Task SaveUser(ViewModel.VM_CreateUser user)
{
var res
Does DeliverAsync return a Task? If it does, try this.
public class MailController : MailerBase
{
public async Task CreateUserAsync(ViewModel.VM_User user)
{
To.Add(user.EmailAddress);
From = System.Configuration.ConfigurationManager.AppSettings["emailSender"];
Subject = "Hi";
await (Email("CreateUser", user).DeliverAsync());
}
}
And then in your controller, await the task returned by CreateUserAsync.
public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
var result = await _user.Save(userDetails);
await (new MailController().CreateUserAsync(user.userDetails));
return Json(new { Success = String.IsNullOrEmpty(result) });
}
note: If your goal is to make sending the email a background fire and forget operation, this is not it.
This is because async
methods track their completion, even async void
. They do this by registering with the SynchronizationContext
when starting and marking the operation complete when returning. ASP.NET tracks all created operations and requires them to all be completed before your action returns, otherwise it returns an error to the HTTP client. If you need to run a “fire and forget” method, you must manually avoid launching it on the ASP.NET SynchronizationContext
. For example, await Task.Run(() => CallFireAndForget())
(await
so that you wait for the synchronous portion to run on the thread pool. This works because CallFireAndForget()
is async void
. To fire a method which returns a Task
as fire-and-forget, you have to explicitly avoid returning the Task
to Task.Run()
like this: await Task.Run(() => { CallFireAndForgetAsync(); })
).
Another way to get the same error message to show up should be to write this in an asynchronous action:
// BAD CODE, DEMONSTRATION ONLY!
AsyncOperationManager.CreateOperation();
If you use the AsyncOperation
API, you have to ensure you call AsyncOperation.OperationCompleted()
prior to allowing the action method to return.
In your case, if the mail sending is really intended to be a fire-and-forget task, you can do the following inside of CreateUser
after changing its signature to async Task CreateUserAsync(ViewModel.VM_User user)
:
await Task.Run(() => Email("CreateUser", user).DeliverAsync());
and in your action:
await new MailController().CreateUserAsync(user.userDetails);
Ideally, you wouldn’t use Task.Run()
for something like this. Instead, you can temporarily replace the current SynchronizationContext
instead (NOTE WELL: Never await
inside the try{}
—instead, if you need to, store the Task
in a local and await it after restoring the original SynchronizationContext
(it may be safer to use a primitive such as SynchronizationContextSwitcher.NoContext() (source) which does the right thing)):
var originalSynchronizationContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(null);
new MailController().CreateUser(user.userDetails);
}
finally
{
SynchronizationContext.SetSynchronizationContext(originalSynchronizationContext);
}