我经常发现在编写库函数时我需要为同步和异步方法实现编写大量代码。有没有办法避免它没有太多的开销?例如,只实现异步版本,然后以在当前线程上同步执行的方式包装它。
答案 0 :(得分:2)
我最喜欢的方法 - 如果同步和异步 必需 - 是布尔参数hack。也就是说,“核心”方法采用bool sync
参数,如果它是真的,那么它保证它返回已经完成的任务。
答案 1 :(得分:0)
如果您的库以多个框架版本为目标,那么由于.NET45之前的版本,最好同时保留异步和非异步版本。
如果不考虑上述问题,则应保留异步版本。调用者应该一直实现async。请参阅this article。
答案 2 :(得分:0)
取决于。
您需要确定常见的内容,并将其放入多个公共成员可以使用的方法中。但常见的将取决于你正在做什么。
例如,如果我有一个复杂的EF查询,我想同步和异步使用:
public IList<TResult> GetStuff(string selector) {
return GetStuffCore(selector).ToList();
}
public async Task<IList<TResult>> GetStuffAsync(string selector) {
return await GetStuffCore(selector).ToListAsync();
}
private IQueryable<TResult> GetStuffCore(string selector)
return from s in ctx.Stuff
where s.Thing.Contains(selector)
// lots of LINQ
select some-complex expression;
}
然而,这种简单的方法不适合有多个可能等待的操作的情况。
答案 3 :(得分:0)
我更喜欢对类/接口负责,例如:
public interface IFoo
{
Task<IBar> DoStuffAsync(string optionalArg, CancellationToken cancellationToken);
}
public static class IFooExtensions
{
public static IBar DoStuff(this IFoo foo, string optionalArg)
=> foo.DoStuffAsync(optionalArg, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
}
这使得类/接口保持清晰意味着只支持并且实际需要方法的异步版本,其中方法的同步版本只是一个可扩展性(帮助程序)。
这同样适用于带有可选参数的重载方法:
public static class IFooExtensions
{
public static Task<IBar> DoStuffAsync(this IFoo foo, CancellationToken cancellationToken)
=> foo.DoStuffAsync(optionalArg: "defaultValue", cancellationToken);
}
当你有多个同一个接口或抽象/基类的实现时,这很有帮助,并且当你需要模拟时很容易在测试中使用,因为你不会混淆你需要模拟的重载方法。