C# 是否需要一项任务<;T>;。转换<;TResult>;推广方法有用还是有隐患?
我正在为Google Cloud API编写客户端库,这些API有一个非常常见的异步帮助器重载模式:C# 是否需要一项任务<;T>;。转换<;TResult>;推广方法有用还是有隐患?,c#,async-await,task,C#,Async Await,Task,我正在为Google Cloud API编写客户端库,这些API有一个非常常见的异步帮助器重载模式: 执行一些简短的同步工作来设置请求 发出异步请求 以简单的方式转换结果 目前我们正在使用异步方法,但是: 转换wait的结果在优先级方面很烦人-我们最终需要(wait foo.Bar().configurewait(false)).TransformToBaz(),括号很烦人。使用两个语句可以提高可读性,但这意味着我们不能使用表达式体方法 我们偶尔会忘记ConfigureAwait(fals
- 执行一些简短的同步工作来设置请求
- 发出异步请求
- 以简单的方式转换结果
- 转换wait的结果在优先级方面很烦人-我们最终需要
,括号很烦人。使用两个语句可以提高可读性,但这意味着我们不能使用表达式体方法(wait foo.Bar().configurewait(false)).TransformToBaz()
- 我们偶尔会忘记
——这在某种程度上可以通过工具解决,但还是有点异味ConfigureAwait(false)
任务添加扩展方法,如下所示:
潜在扩展方法
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<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];
表达式体异步方法
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<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];
公共异步任务TranslateTextAsync(
字符串文本,字符串目标语言)=>
(等待TranslateTextAsync(新[]{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]);
公共任务TranslateTextAsync(
字符串文本,字符串目标语言)=>
TranslateTextAsync(新[]{GaxPreconditions.CheckNotNull(text,nameof(text))},targetLanguage)
.Convert(结果=>结果[0]);
我个人更喜欢最后一个
我知道这会改变验证的时间-在最后一个示例中,为text
传递null
值将立即抛出ArgumentNullException
,而为targetLanguage
传递null
值将返回错误的任务(因为TranslateTextAsync
将异步失败)。这是我愿意接受的区别
我是否应该注意日程安排或绩效方面的差异?(我们仍在构建两个状态机,因为Convert
方法将创建一个状态机。使用Task.ContineWith
可以避免这种情况,但存在博客文章中提到的所有问题。Convert
方法可能会被更改为使用ContinueWith
)
(我有点想在CodeReview上发表这篇文章,但我怀疑答案中的信息将比这是否是一个好主意更有用。如果其他人不同意,我很乐意将其删除。)
转换wait的结果在优先级方面会很烦人
我通常更喜欢引入一个局部变量,但正如您所指出的,它阻止了基于表达式的方法
我们偶尔会忘记ConfigureAwait(false)
——这在某种程度上可以通过工具解决
由于您正在处理库,因此应使用ConfigureAwait(false)
在任何地方,都值得使用代码分析器来执行
configurewait
用法。有一个和一个可以这样做。不过我自己还没有试过
Task.ContinueWith
听起来是个好主意,但我读了斯蒂芬·克利里(Stephen Cleary)的博客文章,建议不要这样做,理由似乎很合理
如果将ContinueWith
,则必须明确指定
TaskScheduler.Default
(这是与等效的ContinueWith
<代码> ConfigureAwait(false)< /> >,并考虑添加标记,如
denychildatach
。在我看来,很难记住如何使用ContinueWith
正确地记住配置等待(false)
另一方面,虽然ContinueWith
是一种低级的、危险的方法,但如果您正确地使用它,它可以给您带来轻微的性能改进。特别是,使用state
参数可以为您节省委派分配。这是TPL和其他Microsoft库通常采用的方法,但我认为它的效率较低这对于大多数库来说,可维护性太高了
它看起来如此简单和有用,以至于我有点惊讶于现在还没有可用的东西
您建议的Convert
方法有。Stephen没有这样说,但我假设,承诺是任务等价物(它们是
两者都有)
另一方面,将这个概念带到一个有趣的地方
结论。转换
/则
是
用于在期货上实现LINQ。Stephen Toub也
(在这一点上有点过时,但很有趣)
我考虑过几次将然后添加到我的AsyncEx库中,
但每一次都没有成功,因为几乎一样
它的唯一好处是通过允许方法链接来解决优先级问题。我假设它不存在于
同样的原因
也就是说,实现您自己的应用程序肯定没有什么错
Convert
方法。这样做将避免使用括号/额外的局部
变量,并允许表达式体方法
我知道这会改变验证的时间
这是我的原因之一(我的博客文章探讨了更多的原因)
在这种情况下,我认为这两种方式都可以,因为“设置请求的简短同步工作”是一种先决条件检查,在我看来,抛出到哪里并不重要(因为它们无论如何都不应该被捕获)
如果“简短的同步工作”更复杂——如果它可以抛出,或者在一年后有人重构它之后可以合理抛出——那么我会使用async
/wait