1

Sometimes, I want to do something with the result of an async method, for example:

var foos = (await GetObjectAsync<IEnumerable<Foo>>()).ToList();

But this notation can be hardly readable because of the parenthesis. If I call another asynchronous method with the result, multiple await expressions are nested, for example:

var result = (await (await GetObjectAsync<IEnumerable<Foo>>()).First().SomeMethodAsync()).GetResult();

I would like to write a more fluent equivalent like:

var foos = GetObjectAsync<IEnumerable<Foo>>()
    .Async()
    .First()
    .SomeMethodAsync()
    .Async()
    .GetResult();

I read the documentation but the only thing that have the right signature (unless I missed something) is Result, and Result is not what I want because it is not an equivalent of await.

Does such a method exist? Can I create an extension to do this if it does not exist?

13
  • 3
    It's a keyword, there is no equivalent method. Also, in my opinion that's a good thing. await turns your code into a state machine behind the scenes, it should not go unnoticed Commented Oct 24, 2018 at 14:10
  • 2
    @maccettura It's impossible to write a method that takes a Task<T> and returns a T that would behave the same as awaiting it. The only possible implementation of such a method is to either not return the actual result, or to synchronously block (unless the task was completed before you called such a method). Commented Oct 24, 2018 at 14:13
  • 2
    @AlexanderDerck where is a keyword, and there is a functional equivalent (Where), so it doesn't seem an unreasonable question. Commented Oct 24, 2018 at 14:16
  • 1
    That is not feasible because await interrupts (most of the time) the current method execution and waits for a "callback" to continue where it left later, therefore a synchronous method equivalent cannot exist. You can chain the continuation and map the ToList on completion but this will return still a Task<T>, not T and then you will have to await on the T or block. ToList is an extension on IEnumerable<T> and not on Task<IEnumerable<T>> so you will need either something like Async(result => result.ToList()) which returns a Task<List<T>> or a blocking call to chain it. Commented Oct 24, 2018 at 14:23
  • 1
    @Boiethios I usually have an extension method to make Task a functor: async Task<TOut> Map(this Task<TIn> t, Func<TIn, TOut> m) => m(await t.CA(False)); so I could do: GetOAsync().Map(r => r.ToList()). Sometimes the fluent syntax makes more sense than the async await. Commented Oct 24, 2018 at 14:29

3 Answers 3

4

You're looking for ContinueWith:

await GetObjectAsync<IEnumerable<Foo>>()
    .ContinueWith(task => task.Result.ToList());

But, this will perform worse and has some complexities that can take a while to understand, like ensuring you're using the correct scheduler for your continuation code (await does this for you).

I'd prefer to instead just split your code onto multiple lines if readability is an issue:

var foosEnumerable = await GetObjectAsync<IEnumerable<Foo>>();
var foos = foosEnumerable.ToList();
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the answer, I'll see what is ContinueWith, and what are the caveat.
@Boiethios ContinueWith is basically Task<TResult> Select<TSource, TResult>(this Task<TSource> source, Func<TSource, TResult> selector) if that sounds familiar : }
ContinueWith isn't going to perform worse, it just takes a lot of boilerplate code to made it actually behave correctly (you mentioned providing a scheduler, but it also doesn't handle errors or cancellation correctly at all). Code that you haven't actually written out.
@Servy there are two Task objects and continuations in the ContinueWith code here, versus one in the await. Tell me how this isn't going to perform worse.
0

To get a fluent syntax you can make a functor and a monad out of Task<T> with the following extension methods:

public static async Task<TResult> Map<T, TResult>(this Task<T> source, Func<T, TResult> func)
{
    var result = await source.ConfigureAwait(false);

    return func(result);
}

public static async Task<TResult> Bind<T, TResult>(this Task<T> source, Func<T, Task<TResult>> func)
{
    var result = await source.ConfigureAwait(false);

    return await func(result).ConfigureAwait(false);
}

This allows you to write your code in a method chain fashion:

var resultTask = GetObjectAsync<IEnumerable<Foo>>()
    .Map(foos => foos.First())
    .Bind(foo => foo.SomeMethodAsync())
    .Map(r => r.GetResult());

Of course I am not considering performances here, but for non performance critical part of code, I prefer readability to micro-optimizations if this helps making your code more understandable (in certain areas I find several cases where this syntax does help).

Also bear in mind that obviously you still get a task back so at some point you will have either to await it or to block with Result or Wait.

Method chaining can be also achieved with ContinueWith as in @Cory Nelson answer (carefully using the right arguments for scheduler and options) avoiding the creation of the async state machine, but for simplicity I used async and await in the extensions which is perfectly safe.

On top of that, if you rename Map to Select and Bind to SelectMany (and add an overload with projection), you can also use the LINQ query syntax on tasks although this probably won't interest you as you are specifically trying to move away from that syntax from what I can read.

3 Comments

@Boiethios are you referring to the argument of the Bind call? That works without async/await if SomeMethodAsync just returns a Task<T>.
@Boiethios yes, on purpose. I changed the name of the variable to resultTask so it was clear that the result is not T but Task<T>. But yes, of course you can await on that or do .Result to get T.
Oh sorry, yes. I'm working meanwhile, so I'm not giving enough attention. I'll delete my comments.
-2

You can use

var foos = GetObjectAsync<IEnumerable<Foo>>().GetAwaiter().GetResult().ToList()

but that is not the same!

If you use "await" keyword, then while waiting for the result the Windows message loop is processed and your application is not frozen (so mouse-events and so on are processed).

If you use the GetAwaiter().GetResult or .Wait(), then the thread is blocked while waiting.

So there is no equivalent for "await". But there is other syntax, that looks similar but behaves very different.

4 Comments

then while waiting for the result the Windows Messageloop is processed and your application is not freezed That is just a side effect to making it asynchronous.
Thats not a side effect, but it is the main reason why the "async" keyword was introduced. To get the same behaviour you previously hat to write something like "var task = Task.start(..); while(!task.finished) { Application.DoEvents(); }"
I had like to have a source for that.
@gofal3 That is not going to behave the same. That's not asynchronous either.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.