Task<T>.Convert<TResult>扩展方法是否有用,或者它是否存在隐患? [英] Would a Task<T>.Convert<TResult> extension method be useful or does it have hidden dangers?

查看:41
本文介绍了Task<T>.Convert<TResult>扩展方法是否有用,或者它是否存在隐患?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为Google Cloud API编写客户端库,这些API具有相当常见的异步帮助器重载模式:

  • 执行一些简短的同步工作以设置请求
  • 发出异步请求
  • 以简单的方式转换结果

目前我们使用的是异步方法,但是:

  • 转换等待的结果在优先级方面很烦人--我们最终需要(await foo.Bar().ConfigureAwait(false)).TransformToBaz(),而括号很烦人。使用两个语句提高了可读性,但这意味着我们不能使用表达式体方法。
  • 我们偶尔会忘记ConfigureAwait(false)-这在某种程度上是可以通过工具解决的,但它仍然有一点味道

Task<TResult>.ContinueWith听起来是个好主意,但我读了Stephen Cleary's blog post建议不要这样做,理由似乎很充分。我们正在考虑为Task<T>添加一个扩展方法,如下所示:

可能的扩展方法

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);
}

然后我们可以非常简单地从同步方法调用它,例如

public async Task<Bar> BarAsync()
{
    var fooRequest = BuildFooRequest();
    return FooAsync(fooRequest).Convert(foo => new Bar(foo));
}

甚至:

public Task<Bar> BarAsync() =>
    FooAsync(BuildFooRequest()).Convert(foo => new Bar(foo));

它看起来如此简单和有用,以至于我有点惊讶还没有可用的东西。

Google.Cloud.Translation.V2代码中,我有两个转换纯文本的方法:一个接受单个字符串,另一个接受多个字符串。单字符串版本的三个选项是(在参数方面略有简化):

常规异步方法

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];
}

基于表达式的异步方法

public async Task<TranslationResult> TranslateTextAsync(
    string text, string targetLanguage) =>
    (await TranslateTextAsync(new[] { GaxPreconditions.CheckNotNull(text, nameof(text)) }, targetLanguage)
        .ConfigureAwait(false))[0];

使用CONVERT的表达式正文同步方法

public Task<TranslationResult> TranslateTextAsync(
    string text, string targetLanguage) =>
    TranslateTextAsync(new[] { GaxPreconditions.CheckNotNull(text, nameof(text)) }, targetLanguage)
        .Convert(results => results[0]);

我个人更喜欢最后一个。

我知道这会改变验证的时间--在最后一个示例中,为text传递null值将立即抛出ArgumentNullException,而为targetLanguage传递null值将返回出错的任务(因为TranslateTextAsync将异步失败)。这是我愿意接受的差别。

在日程安排或性能方面是否有我应该注意的差异?(我们仍在构造两个状态机,因为Convert方法将创建一个状态机。使用Task.ContineWith可以避免这种情况,但存在博客文章中提到的所有问题。Convert方法可能会更改为谨慎使用ContinueWith。)

(我有点想在CodeReview上发布这篇文章,但我怀疑答案中的信息除了这是否确实是一个好主意之外,通常还会更有用。如果其他人不同意,我很乐意提出。)

推荐答案

转换等待的结果最终会使人讨厌优先级

我通常更喜欢引入局部变量,但正如您所指出的,这会阻止表达式体方法。

我们偶尔会忘记ConfigureAwait(false)-这在某种程度上是可以通过工具解决的

由于您正在使用库,使用ConfigureAwait(false) 在任何地方,可能都值得使用代码分析器来强制 ConfigureAwait用法。有一个ReSharper plugin和一个VS plugin可以做到这一点。不过,我自己还没有试过。

Task<TResult>.ContinueWith听起来是个好主意,但我读过Stephen Cleary的博客文章建议不要这么做,理由似乎很充分。

如果使用ContinueWith,则必须显式指定 (这是的等价物 ConfigureAwait(false)),并考虑添加诸如 DenyChildAttach。我很难记住如何使用ContinueWith 比记住ConfigureAwait(false)更准确。

另一方面,虽然ContinueWith是一种低级的、危险的方法,但如果您正确使用它,可以给您带来轻微的性能改进。特别是,使用state参数可以节省委托分配。这是TPL和其他Microsoft库通常采用的方法,但它大大降低了大多数库的可维护性。

它看起来如此简单和有用,以至于我有点惊讶还没有可用的东西。

您建议的Convert方法有existed informally as Then。史蒂芬没有这么说,但我假设这个名字Then来自 JavaScript世界,承诺是任务的等价物(它们是 两者Futures)。

顺便说一句,Stephen's blog post将这个概念带到了一个有趣的地方 结论。Convert/Thenbind for the Future monad,因此 用于实现LINQ-Over-Futures。斯蒂芬·图布也 published code for this(此时已过时,但很有趣)。

我曾多次考虑将Then添加到我的AsyncEx库中, 但每次都没有入围,因为它几乎是一样的 仅为await。它唯一的好处是通过允许方法链接来解决优先级问题。我假设它不存在于 原因相同。

这就是说,实现您自己的当然没有错 Convert方法。这样做可以避免括号/额外的局部 变量并允许使用以表达式为主体的方法。

我知道这会更改验证的时间

这是我wary of eliding async/await的原因之一(我的博客帖子中有更多原因)。

在这种情况下,我认为这两种方式都很好,因为"设置请求的简短同步工作"是一个前提检查,而在我看来,抛出boneheaded exceptions在哪里并不重要(因为它们无论如何都不应该被捕获)。

如果"简短的同步工作"更复杂--如果它是可以抛出的东西,或者在一年后有人重新考虑它后可以合理抛出的东西--那么我会使用async/await。您仍然可以使用Convert来避免优先级问题:

public async Task<TranslationResult> TranslateTextAsync(string text, string targetLanguage) =>
  await TranslateTextAsync(SomthingThatCanThrow(text), targetLanguage)
  .Convert(results => results[0])
  .ConfigureAwait(false);

这篇关于Task&amp;lt;T&amp;gt;.Convert&amp;lt;TResult&amp;gt;扩展方法是否有用,或者它是否存在隐患?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆