结合双同步/异步接口实现



我有同步和异步版本的重复接口方法。每种方法大约有30行长,并且做的事情完全相同。除了中间一个等待Async方法,但另一个调用Synchronous方法。

我当前的模式只是将它们与一个布尔值组合在一起,我称之为";DoAsync";图案,尽管我不记得几年前在哪里见过它。

由于实现保证不会发生异步事件(不处理任务),这是否会改变使用GetAwaiter().GetResult()而不是.Result的理解逻辑?假设同步调用私有方法将失去所有可接受的关注点,这是正确的吗;"同步调用异步方法";因为实现的风格?还有什么变化?或者,将异步和同步方法结合起来做完全相同的事情还有哪些常见的模式?

这就是我所说的模式:

public class MyImplementation : IDoSomething {
public async Task DoSomethingAsync(object input)
=> await DoSomethingInternally(input);
public void DoSomething(object input)
=> DoSomethingInternally(input, false).GetAwaiter().GetResult();
private async Task DoSomethingInternally(object input, bool runSynchronously = false) {
//a dozen other lines of code
object somethingINeed;
if (runSynchronously) somethingINeed = GetSomething(input, lol);
else somethingINeed = await GetSomethingAsync(input, lol);
//a dozen other lines of code
}
}

我不记得几年前在哪里见过它了。

可能来自我的这篇文章。Stephen Toub在对那篇文章进行技术审查时向我介绍了这种模式。

由于实现保证不会发生异步事件(不处理任务),这是否改变了使用GetAwaiter()的理解逻辑。GetResult()结束。后果

GetAwaiter().GetResult()(尽管详细)是可接受的模式。这是因为如何处理异常;CCD_ 4将异常封装在CCD_ 5中。

假设同步调用私有方法将失去所有可接受的关注点";"同步调用异步方法";因为实现的风格?还有什么变化?

是。该模式的工作方式是,如果runSynchronouslytrue,那么它必须返回一个已完成的任务。由于任务已经完成,因此可以安全地阻止,而不会出现任何死锁。从技术上讲;块";这不是一个正确的词,因为任务已经完成;CCD_ 8只是从任务中提取结果(返回值或异常)。

或者,还有什么其他模式可以将异步和同步方法结合起来做完全相同的事情?

我的文章的一个更新是ValueTask<T>现在可用,这意味着不需要为同步路径分配Task<T>

此外,Stephen Toub几周前刚刚发表了一篇博客文章,提到了对模式的进一步改进。你可以做一种把戏;我认为它还没有名字,但我认为它是";"通用代码生成";,因为从逻辑上讲,它类似于C++中的模板代码生成。其思想是,您有一个实现接口的类型,通过将泛型参数约束到该接口,编译器将为每个泛型参数生成不同的代码(它们不同,因为它们是值类型)。此外,由于约束和缺乏继承性,JIT编译器对生成的IL进行良好优化(即内联)的几率极高。

用一个例子解释可能比用文字解释更容易:

interface IDoGetSomething
{
ValueTask<object> DoGetSomethingAsync(object input, Lol lol);
}
struct SyncDoGetSomething : IDoGetSomething
{
public ValueTask<object> DoGetSomethingAsync(object input, Lol lol) =>
new(GetSomething(input, lol));
}
struct AsyncDoGetSomething : IDoGetSomething
{
public async ValueTask<object> DoGetSomethingAsync(object input, Lol lol) =>
await GetSomethingAsync(input, lol);
}

以上是DoSomethingInternally中同步代码和异步代码不同部分的通用代码生成模式。DoSomethingInternally然后参与通用代码生成,如下所示:

private async Task DoSomethingInternally<TDoGetSomething>(object input)
where TDoGetSomething : IDoGetSomething
{
//a dozen other lines of code
object somethingINeed = await default(TDoGetSomething).DoGetSomethingAsync(input, lol);
//a dozen other lines of code
}

并暴露为:

public Task DoSomethingAsync(object input)
=> DoSomethingInternally<AsyncDoGetSomething>(input);
public void DoSomething(object input)
=> DoSomethingInternally<SyncDoGetSomething>(input).GetAwaiter().GetResult();

这就是模式的结束。也许有点冗长,但它确实提供了一个很好的好处:所有同步/异步差异都在一个单独的地方(IDoGetSomething及其实现),而不是分散在您的实现方法中。

旁注:由于这是使用泛型代码生成,因此将生成两个独立的DoSomethingInternally方法,一个绑定到AsyncDoGetSomething,另一个绑定至SyncDoGetSomething。以前只有一个方法采用bool(运行时)参数。

C#11引入了静态接口方法,因此通用代码生成变得更好,避免了尴尬的default(T)构造:

interface IDoGetSomething
{
static abstract ValueTask<object> DoGetSomethingAsync(object input, Lol lol);
}
struct SyncDoGetSomething : IDoGetSomething
{
public static ValueTask<object> DoGetSomethingAsync(object input, Lol lol) =>
new(GetSomething(input, lol));
}
struct AsyncDoGetSomething : IDoGetSomething
{
public static async ValueTask<object> DoGetSomethingAsync(object input, Lol lol) =>
await GetSomethingAsync(input, lol);
}
private async Task DoSomethingInternally<TDoGetSomething>(object input)
where TDoGetSomething : IDoGetSomething
{
//a dozen other lines of code
object somethingINeed = await TDoGetSomething.DoGetSomethingAsync(input, lol);
//a dozen other lines of code
}
// (the rest is unchanged)
public Task DoSomethingAsync(object input)
=> DoSomethingInternally<AsyncDoGetSomething>(input);
public void DoSomething(object input)
=> DoSomethingInternally<SyncDoGetSomething>(input).GetAwaiter().GetResult();

C#方法是彩色的

不幸的是,C#没有任何关于如何调用方法的抽象。C#方法是有色的,这是无法回避的。

一种替代方案是仅具有async实现,而不是同时具有async和非async。在您的情况下,您将丢弃public void DoSomething(object input)方法(并将xyzInternally方法的主体移动到async方法)。

不幸的是,这导致了一种令人讨厌的情况,即您必须使用async/await从异步方法一直向上调用堆栈。它被称为";异步癌症;并且是由C#方法被着色的事实引起的。


我可能错过了大局。如果是这样的话,那么请再提供几个例子,这样我就可以看到你看到的相同模式。


p.S.Task.GetAwaiter().GetResult()Task.Result基本相同。是的,异常的产生方式是不同的,但两者都很容易导致死锁(以及其他问题)。例如:您能保证await GetSomethingAsync(input, lol)不会重新进入DoSomethingInternally吗?

这个怎么样:

public async Task DoSomethingAsync(object input)
{
var lol = FirstDozenLinesWrapperMethod();
await GetSomethingAsync(input, lol);
LastDozenLinesWrapperMethod();
}
public void DoSomething(object input)
{
var lol = FirstDozenLinesWrapperMethod();
GetSomething(input, lol);
LastDozenLinesWrapperMethod();
}

最新更新