I'm writing client libraries for Google Cloud APIs which have a fairly common pattern for async helper overloads:
Currently we're using async methods for that, but:
(await foo.Bar().ConfigureAwait(false)).TransformToBaz()
and the brackets are annoying. Using two statements improves readability, but means we can't use an expression-bodied method.ConfigureAwait(false)
- this is solvable with tooling to some extent, but it's still a bit of a smellTask<TResult>.ContinueWith
sounds like a good idea, but I've read Stephen Cleary's blog post recommending against it, and the reasons seem sound. We're considering adding an extension method for Task<T>
like this:
Potential extension method
public static async Task<TResult> Convert<TSource, TResult>( this Task<TSource> task, Func<TSource, TResult> projection) { var result = await task.ConfigureAwait(false); return projection(result); }
We can then call this from a synchronous method really simply, e.g.
public async Task<Bar> BarAsync() { var fooRequest = BuildFooRequest(); return FooAsync(fooRequest).Convert(foo => new Bar(foo)); }
or even:
public Task<Bar> BarAsync() => FooAsync(BuildFooRequest()).Convert(foo => new Bar(foo));
It seems so simple and useful that I'm slightly surprised there isn't something already available.
As an example of where I'd use this to make an expression-bodied method work, in the Google.Cloud.Translation.V2
code I have two methods to translate plain text: one takes a single string and one takes multiple strings. The three options for the single-string version are (simplified somewhat in terms of parameters):
Regular async method
public async Task<TranslationResult> TranslateTextAsync( string text, string targetLanguage) { GaxPreconditions.CheckNotNull(text, nameof(text)); var results = await TranslateTextAsync(new[] { text }, targetLanguage).ConfigureAwait(false); return results[0]; }
Expression-bodied async method
public async Task<TranslationResult> TranslateTextAsync( string text, string targetLanguage) => (await TranslateTextAsync(new[] { GaxPreconditions.CheckNotNull(text, nameof(text)) }, targetLanguage) .ConfigureAwait(false))[0];
Expression-bodied sync method using Convert
public Task<TranslationResult> TranslateTextAsync( string text, string targetLanguage) => TranslateTextAsync(new[] { GaxPreconditions.CheckNotNull(text, nameof(text)) }, targetLanguage) .Convert(results => results[0]);
I personally prefer the last of these.
I'm aware that this changes the timing of the validation - in the final example, passing a null
value for text
will immediately throw an ArgumentNullException
whereas passing a null
value for targetLanguage
will return a faulted task (because TranslateTextAsync
will fail asynchronously). That's a difference I'm willing to accept.
Are there differences in scheduling or performance that I should be aware of? (We're still constructing two state machines, because the Convert
method will create one. Using Task.ContineWith
would avoid that, but has all the problems mentioned in the blog post. The Convert
method could potentially be changed to use ContinueWith
carefully.)
(I'm somewhat tempted to post this on CodeReview, but I suspect the information in the answers will be more generally useful beyond whether this is specifically a good idea. If others disagree, I'm happy to move it.)
In C#, the extension method concept allows you to add new methods in the existing class or in the structure without modifying the source code of the original type and you do not require any kind of special permission from the original type and there is no need to re-compile the original type.
If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.
2 Answers. Show activity on this post. If you use await in your code, you are required to use the async keyword on the method. If you use async and want to return an actual type, you can return the type as a generic Task like this Task<int> .
An extension method is just a normal static method, with syntactic sugar to invoke it. So work out how you'd make a normal method asynchronous (e.g. by starting a Task within it, in . NET 4) and go from there.
Transforming the result of await ends up being annoying in terms of precedence
I generally prefer to introduce a local var, but as you noted, that prevents expression-bodied methods.
We occasionally forget
ConfigureAwait(false)
- this is solvable with tooling to some extent
Since you're working on a library and should use ConfigureAwait(false)
everywhere, it may be worthwhile to use a code analyzer that enforces ConfigureAwait
usage. There's a ReSharper plugin and a VS plugin that do this. I haven't tried them myself, though.
Task<TResult>.ContinueWith
sounds like a good idea, but I've read Stephen Cleary's blog post recommending against it, and the reasons seem sound.
If you used ContinueWith
, you'd have to explicitly specify TaskScheduler.Default
(this is the ContinueWith
equivalent of ConfigureAwait(false)
), and also consider adding flags such as DenyChildAttach
. IMO it's harder to remember how to use ContinueWith
correctly than it is to remember ConfigureAwait(false)
.
On the other hand, while ContinueWith
is a low-level, dangerous method, if you use it correctly then it can give you minor performance improvements. In particular, using the state
parameter can save you a delegate allocation. This is the approach commonly taken by the TPL and other Microsoft libraries, but IMO it lowers the maintainability too much for most libraries.
It seems so simple and useful that I'm slightly surprised there isn't something already available.
The Convert
method you suggest has existed informally as Then
. Stephen doesn't say so, but I assume that the name Then
is from the JavaScript world, where promises are the task equivalent (they are both Futures).
On a side note, Stephen's blog post takes this concept to an interesting conclusion. Convert
/Then
is the bind
for the Future monad, so it can be used to implement LINQ-over-futures. Stephen Toub has also published code for this (rather dated at this point, but interesting).
I have thought a few times about adding Then
to my AsyncEx library, but each time it didn't make the cut because it's pretty much the same as just await
. Its only benefit is solving the precedence problem by allowing method chaining. I assume it doesn't exist in the framework for the same reason.
That said, there's certainly nothing wrong with implementing your own Convert
method. Doing so will avoid the parenthesis / extra local variable and allow for expression-bodied methods.
I'm aware that this changes the timing of the validation
This is one of the reasons that I'm wary of eliding async
/await
(my blog post goes into more reasons).
In this case, I think it's fine either way, since the "brief synchronous work to set up a request" is a preconditions check, and IMO it doesn't matter where boneheaded exceptions are thrown (because they shouldn't be caught anyway).
If the "brief synchronous work" was more complex - if it was something that could throw, or could reasonably throw after someone refactors it a year from now - then I would use async
/await
. You could still use Convert
to avoid the precedence issue:
public async Task<TranslationResult> TranslateTextAsync(string text, string targetLanguage) => await TranslateTextAsync(SomthingThatCanThrow(text), targetLanguage) .Convert(results => results[0]) .ConfigureAwait(false);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With