Callum Linington
Callum Linington

Reputation: 14417

Propagated Async/Await Method Calls

So I have this method in my base controller:

protected async Task<KeyValuePair<bool, string>> ExecuteSimpleQuery(Func<Task<bool>> service)
{
    var success = false;
    var message = string.Empty;

    try
    {
        success = await service.Invoke();
    }
    catch (Exception exception)
    {
        message = exception.Message;
        success = false;
    }

    return new KeyValuePair<bool, string>(success, message);
}

I want to use it like this:

public async Task<ActionResult> Login(RegisterDto register)
{
    var objectStore =
        await this.ExecuteSimpleQuery(async () => await this.securityService.LoginAsync(register.UserName, register.Password, true));

    if (objectStore.Key)
    {
        return this.RedirectToAction("Index", "Toolbox");
    }

    this.TempData["error"] = objectStore.Value;
    return this.View(register);
}

So what I'm passing through to ExecuteSimpleQuery is an awaitable method LoginAsync, I just want to make sure I'm properly awaiting the method.

My thought process is:

  1. LoginAsync returns a Task<bool> therefore the Func has to return that.
  2. When passing it, you can await it, so do.
  3. Because the Func returns a Task<bool> inside ExecuteSimpleQuery you can await it there, so should
  4. ExecuteSimpleQuery is awaiting a method, therefore has to have the async keyword and therefore has to return Task<T>.
  5. The last point propagates to the Login action, that method returns Task<T> so it can be awaited, so should.

Am I even close?

Upvotes: 2

Views: 1056

Answers (1)

usr
usr

Reputation: 171206

This will work. It can be simplified a little bit:

async () => await this.securityService.LoginAsync(register.UserName, register.Password, true)

could be written as

() => this.securityService.LoginAsync(register.UserName, register.Password, true)

because LoginAsync already returns a Task. That said always awaiting tasks whenever you process them has a certain consistency to it. It isn't really harmful to wrap this task the way you did. I think both ways are reasonable.

If LoginAsync can not throw any exception (that you care about) you don't need the lambda at all. Exceptions are usually stored in the Task when you call an async method. You could directly pass a Task to ExecuteSimpleQuery. If LoginAsync does not adhere to this pattern you cannot do this because the exception would be triggered too early.

   var loginTask = securityService.LoginAsync(register.UserName, register.Password, true);
   var objectStore = await ExecuteSimpleQuery(loginTask);

protected async Task<KeyValuePair<bool, string>> ExecuteSimpleQuery(Task<bool> service)
{
    //...

    try
    {
        success = await service;
    }
    catch (Exception exception)
    {
        //...
    }

    //...
}

You can test this by saying:

   var loginTask = Task.Run(() => { throw null; });

The catch will be hit.

Upvotes: 3

Related Questions