2

What are differences between the 3 calls inside method WhatDifferences?

Here is test code:

async Task WhatDifferences(Context context)
{
    await ActionAsync(context, async x => await IsOddAsync(x).ConfigureAwait(false));
    await ActionAsync(context, x => IsOddAsync(x));
    await ActionAsync(context, IsOddAsync);
}

async Task<T> ActionAsync<T>(Context context, Func<Context, Task<T>> action)
{
    return await action(context).ConfigureAwait(false);
}

async Task<bool> IsOddAsync(Context context)
{
    return await Task.Run(() => context.Count++ % 2 == 1).ConfigureAwait(false);
}

class Context
{
    public int Count { get; set; }
}

I'm trying to decide which one to use in my codebase and based on my knowledge all 3 behave the same.

The question is different with What's the method signature for passing an async delegate?

You may know my concern if I show more logic

async Task<T> ActionAsync<T>(Context context, Func<Context, Task<T>> action)
{
    using (var transaction = new TransactionScope())
    {
        //do some async logic before action
        var result = await action(context).ConfigureAwait(false);
        //do other async validation logic after action
        return result;
    }
}
Dongdong
  • 2,208
  • 19
  • 28
  • 5
    If you can limit your question to what the differences are between the calls, the question would be on-topic. Asking which one is recommended, or "best", asks for opinions, which is explicitly off-topic. – Heretic Monkey Sep 20 '19 at 18:52
  • Possible duplicate of [What's the method signature for passing an async delegate?](https://stackoverflow.com/questions/8511466/whats-the-method-signature-for-passing-an-async-delegate) – Chamika Sandamal Sep 20 '19 at 19:03
  • @Alexei Thanks for edit. @Chamika, the question is different with it. I know when to use Action and Func, but I do need to know the differences from 3 listed calls for Func>. We have a core `transaction` function which is refferenced 300+ times, but now I need refactor it and call it with a best way. I don't want to break transaction and threads, that's the question where comes out. Thanks. – Dongdong Sep 20 '19 at 19:12
  • Here is why ConfigureAwait(false) is a good idea: https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f –  Sep 20 '19 at 19:23
  • `await ActionAsync(context, x => IsOddAsync(x));` and `await ActionAsync(context, IsOddAsync);` are pretty much the same, the first one is creating func to call the the func, the later is just passing the func directly. As For the ConfigureAwait that is used in older versions for to prevent deadlocks. In .Net-Core its obsolete because they've gotten rid of the synchronization context, see this [answer as reference](https://stackoverflow.com/a/13494570/1938988) – johnny 5 Sep 20 '19 at 19:27
  • 2
    This question is frequently asked, and I am always mystified by it because it is unclear what you mean by "the differences". Here's an exercise to help clarify. Suppose I describe these workflows. Workflow 1: eat a sandwich. Workflow 2: Write a to-do list that says "eat a sandwich" and then do what is on the list. Workflow 3: Write a to-do list that says "ask a friend to write a to-do list that says to eat a sandwich and then take the list from the friend and do what is on the list" and then do what is on the list. **Can you list all the differences between these three workflows?** – Eric Lippert Sep 20 '19 at 19:35
  • 1
    If you cannot, then I put to you that we cannot answer your similar question about code samples because you do not have a consistent definition of "the differences". If you can list the differences, list them, and that will give us an idea of the sorts of differences you're looking for. If you think there are no differences because in every case the outcome is the same -- a sandwich is eaten -- then the answer to your question is "there are no differences between those workflows". – Eric Lippert Sep 20 '19 at 19:36
  • @EricLippertE Thank Eric, Can you pair the 3 workflows to the 3 calls. I guess the 3rd call is workflow 1 or 3, and the 2nd call is workflow 2. I want to call workflow 1 only. – Dongdong Sep 20 '19 at 19:44
  • @Dongdong `await ActionAsync(context, IsOddAsync)` == "eat a sandwich". All the other ways involve creating one or more to-do lists to eat your sandwich. – Gabriel Luci Sep 20 '19 at 19:47

2 Answers2

4

I'm trying to decide which one to use in my codebase and based on my knowledge all 3 behave the same.

In this specific instance, this is essentially true.

This one creates a delegate that refers to the IsOddAsync method:

await ActionAsync(context, IsOddAsync);

This one creates a method for the lambda expression and the delegate refers to that compiler-generated method:

await ActionAsync(context, x => IsOddAsync(x));

And this one does the same, but for an asynchronous lambda, so the compiler-generated method also has an async state machine:

await ActionAsync(context, async x => await IsOddAsync(x).ConfigureAwait(false));

In general, your question boils down to two questions:

  1. Should I use method groups instead of lambdas? Yes, you should. There's no disadvantage to doing so, and it's a tiny bit more efficient, shorter code, without any impact on maintainability.
  2. Should I elide async/await or keep the keywords in? This one is more nuanced.

Eliding async in this particular case is fine, because all the async lambda is doing is calling a single method and passing its parameter. There's no possibility of exceptions being thrown from the lambda before or after the call to IsOddAsync.

However, if your lambda is more complex - doing operations on x before passing it to IsOddAsync, or doing operations on the result, or using a using block, then you'd want to keep the async/await keywords for maximum maintainability. More information here.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
3

await vs return Task

The difference between:

await ActionAsync(context, async x => await IsOddAsync(x).ConfigureAwait(false));
await ActionAsync(context, x => IsOddAsync(x));

In some cases you don't need the await (and also not the async of course)

Methods that perform asynchronous operations don't need to use await if:

  • There is only one asynchronous call inside the method
  • The asynchronous call is at the end of the method
  • Catching/handling exception that may happen within the Task is not necessary

See Returning a Task without await.

In that case you could return the Task intermediately.

Please note there is a small difference in behavior - depending on the implementation of IsOddAsync:

Important: Returning the Task instead of awaiting it, changes the exception behavior of the method, as it won't throw the exception inside the method which starts the task but in the method which awaits it.

As Gabriel Luci noted, with the current implementation of IsOddAsync (one call and an await), there is no difference in behavior.

x => IsOddAsync(x) vs IsOddAsync

The difference between

await ActionAsync(context, x => IsOddAsync(x));
await ActionAsync(context, IsOddAsync);

In the first one your are creating an anonymous (lambda) method with the parameter x. As IsOddAsync has also one parameter (with the same type), there is no need for the lambda method.

Please note you need the lambda if IsOddAsync has other parameters, e.g. and 2nd parameter, then you need the lambda. Example:

await ActionAsync(context, x => IsOddAsync(x, "mySecondParameter"));

In this case there is no difference in behavior, except the callstack when an exception in thrown inside.

Community
  • 1
  • 1
Julian
  • 33,915
  • 22
  • 119
  • 174
  • It should also be noted that the difference *in the results* of all three methods is absolutely nothing. – Gabriel Luci Sep 20 '19 at 19:48
  • Well the first comparison has a subtle difference ;) (I will make that more clear in the post, thanks!) – Julian Sep 20 '19 at 19:50
  • Yes and no. The `ConfigureAwait` is there, but it's already being used in `ActionAsync`, so it has no real (useful) effect in the lambda. – Gabriel Luci Sep 20 '19 at 19:51
  • Good one, I didn't really reasoned from the body of `IsOddAsync`. I only took the 3 calls in scope (and the others out of scope / encapsulated) – Julian Sep 20 '19 at 19:53
  • I was referring to the use of `ConfigureAwait` in `ActionAsync`, where it uses it on the `Task` that is returned from the method passed in. So whether it's used in the lambda or in `IsOddAsync` makes no difference. – Gabriel Luci Sep 20 '19 at 20:13
  • But I was really pointing out that there is no difference in the data being returned by `ActionAsync`. All three will get the same data in the end. – Gabriel Luci Sep 20 '19 at 20:15
  • @Julian, Thanks for your answer! I also learned from the post. and your answer is very close to Stephens. I would up-vote both answers and choose his as answer. Thanks. – Dongdong Sep 25 '19 at 18:32
  • True, except I was a day earlier ;) – Julian Sep 25 '19 at 18:42