0

I have Login post action:

    [HttpPost]
    public ActionResult Login(LoginModel model, string post)
    {
        Repo.Resolve<ILogsRepository>().LogLoginAttempt(model.Username, this.Request.UserHostAddress, this.Request.RawUrl);

        if (ModelState.IsValid) {
            var login = this._authRepository.ValidateCredentials(model.Username, model.Password);
            if (login != null) {
                Logger.LogAsync(Action.UserSignIn, Level.Log, string.Format("User signed in as '{0}' -> '{1}'", model.Username, login));

                return Request.IsAjaxRequest() ? Content("__close__") : ClientCabinet();
            }

            Logger.LogAsync(Action.UserSignIn, Level.Mandatory, Event.Error, string.Format("User failed to sign in as '{0}': invalid username or password", model.Username));

            ModelState.AddModelError("Username", @"login or password is incorrect!");
        }

        return View(model);
    }

And i would like to make logger method async:

    public static async Task LogAsync(Action action, Level level, string message) {
        Log(action, level, message);
    }

So, i want to achieve async actions of Logger after ValidateCredentials returns me correct result.

But, if i add breakpoint into LogAsync or Thread.Sleep, method blocks and still processing synchronuosly.

How can i achieve async execution of method after login is validate?

Maxim Zhukov
  • 10,060
  • 5
  • 44
  • 88

2 Answers2

1

I think you are confused on what await will do. The await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes. The task represents ongoing work.

In your case it looks like you would rather just "fire-and-forget" by having a new thread handle that work and you just carry on with the current method.(Not something I necessarily recommend because you wont know if something goes wrong unless you wait for the task to complete and handle that exception, but if you don't care then its okay).

If that's so then just fire off a task and it will have the asynchronous behavior you are looking for:

Task.Run(() => Log(action, level, message));

so in your code it would look like this:

[HttpPost]
public ActionResult Login(LoginModel model, string post)
{
    Repo.Resolve<ILogsRepository>().LogLoginAttempt(model.Username, this.Request.UserHostAddress, this.Request.RawUrl);

    if (ModelState.IsValid) {
        var login = this._authRepository.ValidateCredentials(model.Username, model.Password);
        if (login != null) {
            Task.Run(() => Logger.Log(Action.UserSignIn, Level.Log, string.Format("User signed in as '{0}' -> '{1}'", model.Username, login)));

            return Request.IsAjaxRequest() ? Content("__close__") : ClientCabinet();
        }

       Task.Run(() => Logger.Log(Action.UserSignIn, Level.Mandatory, Event.Error, string.Format("User failed to sign in as '{0}': invalid username or password", model.Username)));

        ModelState.AddModelError("Username", @"login or password is incorrect!");
    }

    return View(model);
}

Remember that you will need to include the System.Threading.Tasks namespace.

If you do care about the exception that may occur I would do something more along the lines of:

task1 = Task.Run(() => Log(arg1, arg2, arg3));

try
{
    task1.Wait();
}
catch (AggregateException ae)
{
    // Assume we know what's going on with this particular exception. 
    // Rethrow anything else. AggregateException.Handle provides 

    foreach (var e in ae.InnerExceptions)
    {
        if (e is MyCustomException)
        {
            Console.WriteLine(e.Message);
        }
        else
        {
            throw;
        }
    }

}
Jason Roell
  • 6,679
  • 4
  • 21
  • 28
1

It looks like you want to return a response to the user ASAP, and then 'sometime in the future' log that the user signed in. This, or something similar, is answered in the following post: Fire and forget async method in asp.net mvc.

The takeaway point from that post, at least for me, is this. Fire-And-Forget methods should only be used if you do not care if the method fails. In this case, if it is important to you that you log this information about users logging in, you should not go down this path.

Perhaps it depends on how long this logging operation takes, and how long is acceptable to you. One strategy for this kind of thing is to use a message queue. That is, you post the information you require to be logged into a message queue, and some other process, perhaps on another machine, reads the message from the queue and does the actual logging. Although I can't see this being substantially quicker that just logging the information in the first place.

Community
  • 1
  • 1
polytopia
  • 419
  • 4
  • 11