Using the result of an async method

206 Views Asked by At

I have a simple async method with the signature:

public async Task<bool> AllowAccessAsync(string authenticatedUserToken)

When calling this method, I appear to have two options when assigning its result to a local variable:

bool response = null;
// option 1
await this.transportService.AllowAccessAsync(authenticatedUserToken).ContinueWith(task => response = task.Result);
// option 2
response = await this.transportService.AllowAccessAsync(authenticatedUserToken);

The first uses a continuation delegate to assign to the local variable, the second assigns the result directly to the variable.

Do these have the same result? Are there any advantages to either approach? Is there a better way to do this?

4

There are 4 best solutions below

4
On BEST ANSWER

Do these have the same result?

Edit:

@Servy points out correctly that since the ContinueWith is simply a projection of the result. This means that both operations are semantically equivalent, but exception wise they'll behave differently.

Are there any advantages to either approach? Is there a better way to do this?

Edit 2:

The below remark relate to the use of async-await vs ContinueWith, generally. Looking specifically at both examples as they both use async-await, definitely use the latter, as both contain state-machine generation, but the latter will propagate an AggregateException in case an exception occurs.

async-await has the minimal overhead of producing a state-machine, while ContinueWith doesn't. On the other hand, using async-await lets you "feel" synchronous while actually being asynchronous, and saves you the verbosity of ContinueWith. I would definitely go with async-await, though I advise you to research the right path of using it, as there can be unexpected pitifuls.

0
On

Do these have the same result?

Yes, both options will eventually set the same result into response. The only difference happens when there's an exception. In the first option The exception thrown will be an AggregateException wrapper over the actual exception while in the second one it will be the actual exception.

Are there any advantages to either approach?

There is absolutely no advantage to use ContinueWith in such a way. The second option has the advantages of having better exception handling and being much simpler to write and read.

Is there a better way to do this?

Not really, this is how you use async-await.


As Servy commented, this wasn't how ContinueWith was meant to be used. The ContinueWith equivalent is to put the rest of the method into the continuation. So instead of this:

public async Task FooAsync()
{
    var response = await this.transportService.AllowAccessAsync(authenticatedUserToken);
    Console.WriteLine(response);
}

You would do this:

public Task FooAsync()
{
    return this.transportService.AllowAccessAsync(authenticatedUserToken).
        ContinueWith(task => Console.WriteLine(task.GetAwaiter().GetResult()));
}

This does have some performance advantages as it doesn't need the async-await state machine but it's very complicated to get right.

0
On

By using async/await with Task Parallel Library's ContinueWith you're mixing patterns unnecessarily. There's a whole host of reasons not to do this unless you have no choice, not least of which is that async/await's SynchronizationContext is not preserved by the default implementation of ContinueWith.

So in short, option 2 is correct.

0
On

It has more to do with coding style.

The first style might be useful when you have a large workflow which might want to assign different continuations when executing the promise. However it relies on closures. I would not recommend this usage since it's not always predictable. The ContinueWith should be used when you create workflows and each step relies only on the previous ones, and does not communicate with the outside scope, except by delivering a task that yields the final result (which you would then await).

The second one is useful when you are just interested in the result.

Also ContinueWith allows you to specify a TaskScheduler since the one that comes default with your application might not be the one you want.

More information on TaskScheduler here: http://blog.stephencleary.com/2015/01/a-tour-of-task-part-7-continuations.html