Always use the 'async' and 'await' keywords in asynchronous methods in a library?
Asked Answered
D

2

16

Summary: In a library method, when should I use the async and await keywords instead of returning a Task directly?

I believe my question is related to this one. However, that question is about .NET 4.0 and the TPL, while I'm using .NET 4.6 with the async and await keywords. So, I think my question might get different answers because these keywords didn't exist when the linked question was answered.

Explanation: I'm writing a simple wrapper for an external WCF service and the wrapper makes multiple SendAsync calls. Now I think that each wrapper method should just return a Task<> directly without being awaited. My understanding is that async/await should be used on the application layer, and not within a library.

So, for example, here is the approach that I think I should take for each wrapper method:

private Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    return _service.SendAsync(request);
}

But on the Internet, I found several posts that use this approach instead:

private async Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    return await _service.SendAsync(request).ConfigureAwait(false);
}

And here is another example that I found on technet:

async Task PutTaskDelay()
{
    await Task.Delay(5000);
} 

private async void btnTaskDelay_Click(object sender, EventArgs e)
{
    await PutTaskDelay();
    MessageBox.Show("I am back");
}

So, when should I use the second approach (the one that includes the async and await keywords)? Why not just return a whole Task without making PutTaskDelay async? I think that I should return Task directly whenever it is possible, and use async/await to get a final result in the application layer only. Am I right? If not, what is the difference between the two approaches that I show here?

My concern: When the async and await keywords are used, it seems that it just provides additional work to the compiler without any benefit.

Dysplasia answered 14/3, 2016 at 12:18 Comment(9)
Possible duplicate?Tonnie
You don't fully understand what the async keyword means. Whether or not you use it in your code, the caller would never know about it. Even the runtime it self has no notion of async / await.Convertite
async/await are implementation details of your methods. It matters not one jot whether your method is declared async Task Method() or just Task Method(), so far as your callers are concerned. (In fact, you are free to change between these two at a later point in time without it being considered a breaking change)Phenomena
@Alex - I think your question is a good one and I did a fair amount of editing to hopefully make it even more useful to others and bring more attention to it. I think I faithfully preserved the intent of your question...if not, please correct any misunderstandings I may have. Also, in your code examples, I changed SignAsync to SendAsync. I hope that was what you intended.Farce
@Farce thank you for editing. No, when i wrote SignAsync I meant async Sign method, because I called it to get a digital signature for byte array in some certification center. But I won't edit this post becuase method name Sign or Send doesn't matter. I consider that intent is actually preserved, so I just thank you for a good job :)Dysplasia
@Alex - In your original question, you stated: "Explanation: I'm writing a simple wrapper for an external WCF service, so I have multiple SendAsync calls." So, I was a bit confused when I saw SignAsync in your code examples. Also, after quickly checking MSDN, the versions of SendAsync and SignAsync that I looked at both take more than one parameter. So, if you think you can make the code examples more realistic, I encourage you to do so. That's because some people might arrive at your question looking for SendAsync or SignAsync.Farce
@Farce I got it, SendAsync is fine, because as I said, specific method name doesn't matter in this case.Dysplasia
@Alex - I certainly agree that the use of SendAsync or SignAsync isn't central to your question. Our comments should clear up any confusion that someone might have if they arrive here looking for SendAsync or SignAsync.Farce
Related: At the end of an async method, should I return or await? and What is the purpose of “return await” in C#?.Farce
N
9

Should I use async await in library?

It all depends. If you're going to take advantage of the asynchronous programming paradigm, then the answer is "yes," the async and await keywords are needed most of the time. More than likely, you will find yourself needing to use async/await. That is because in most situations it would be difficult to use only Task and Task<T> as you will more than likely need to reason about the results of the async operations that you invoke.

Additionally, based on your question it seems as though you may have some confusion about the keywords themselves and how they relate to the Task and Task<T> types. Allow me to clarify this for you.

The async keyword allows a method to use the await keyword. The best practice is to have all async methods return either Task or Task<T> unless you are unable to (for example, a button click event handler as you demonstrated above).

Methods that return Task or Task<T> represent asynchronous operations. When you are in a library it is suggested to always use .ConfigureAwait(false) for reasons detailed here. Additionally, I always point people to this detailed article on the subject.

To differentiate the two approaches in your question:

The method below returns a Task<SignResponse>. This is an async operation that represents the work to sign in. The method can be awaited by the caller to get SignResponse.

private Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    return _service.SignAsync(request);
}

Likewise, this version does the same thing...except that the async/await keywords are not needed. The reason they are not needed is that the method itself does not need to use SignResponse and therefore it could simply return Task<SignResponse> as shown above. And as you indicated in your question, there is indeed a penalty when you use the async/await keywords when they are not needed. Doing so adds an extra state machine step as the result is yielded, since its awaited.

private async Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    return await _service.SignAsync(request).ConfigureAwait(false);
}

Finally, if you needed to reason about the response, you could use the aforementioned keywords to do so like this:

private async Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    var result = await _service.SignAsync(request).ConfigureAwait(false);
    if (result.SomeProperty == SomethingWeCareToCheck)
    {
        _log.Log("Wow, this was un-expected...");
    }
    return result;
}
Netsuke answered 16/3, 2016 at 13:0 Comment(3)
Might be worth mentioning that there is a feature request for the compiler to be made smarter about return await (where that's the only await in the method).Phenomena
In a nutshell, I should use a direct task return whenever is possible, however, when I need a result for some reason or I want to run another task after this one ends, I should mark it as async/await. Of course I can write it manually with ContinueWith, but it's better to use built-in functionallity. Of course, we should not forget about ConfigureAwait. Some helpful links I readed before asking the question (just in case): Zen of async, TPL PatternDysplasia
I also found that await should be also used when exception handling should be done here. I mean if you just return a Task (from WCF service for example) and it throws an exception, you cannot log it becuase it fails in internal WCF's await. But when you await it in service, you can use unhandled exception handler to log it.Dysplasia
E
-2

Don't take my word for it, because I never understood async/await that well, but what bothered me most about the idea was that all methods using async had to be marked as async as well, which annoyed me to no end.

I guess in a library giving people the option to choose how to use the methods is a good thing, so you should use async, however I always found it more clear to explicitly use the Tasks directly.

Eastsoutheast answered 16/3, 2016 at 12:36 Comment(5)
Actually, this is a bad idea. Either the method is synchronous or not. Using Tasks is no different from using async/await, these keywords are just sugar around Tasks. The caller will never now that a library function is marked asyncWira
unless you want to use await and the method is not marked async, therefore it won't work :) My advice was to mark the methods with async, as this will allow both scenarios: await or just Tasks, like I do it.Eastsoutheast
@SideriteZackwehdex - but the decision on whether to use async/await in any particular method is a local decision. It impacts neither code that calls yours nor code you call into. As I said in a comment to the question, it's an implementation detail, and you're free to add or remove them from any particular method.Phenomena
How can you say that when async marks the method you are calling and await is in the code you are writing? It seems to me that it impacts both caller and callee. The library needs to implement async methods in order to enable the use of await... or am I getting it wrong here?Eastsoutheast
No, for the caller, all that matters is whether your method returns SomeType or Task<SomeType>. That decision (return type) is completely separate from async/await. It is true that if you take a mostly synchronous code base and want to make it asynchronous, you will frequently be both changing method return types and decorating functions with async/await, but be very clear that those are different actions.Phenomena

© 2022 - 2024 — McMap. All rights reserved.