对于同步和异步版本,我有重复的接口方法。每种方法都有30行长,并且做的事情完全一样。除了中间有一个等待异步方法,而另一个则调用同步方法。
我当前的模式只是将它们与我所称的"DoAsync“模式结合在一起,尽管我不记得几年前我在哪里看到过它。
如果实现保证不发生异步事件(不处理任务),这是否改变了在.Result上使用.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
}
}发布于 2022-09-22 02:47:37
我不记得几年前我在哪里看到的。
可能是我的这篇文章。Stephen在对这篇文章进行技术审查时,向我介绍了这种模式。
如果实现保证不发生异步事件(不处理任务),这是否改变了在GetAwaiter().GetResult()之上使用.Result的理解逻辑?
GetAwaiter().GetResult() (虽然冗长)是公认的模式。这是因为异常是如何处理的;Result将在AggregateException中包装异常。
假设同步调用私有方法会因为实现的风格而失去“同步调用异步方法”的所有已接受的关注点,这是否正确?还有什么变化吗?
是。模式的工作方式是,如果runSynchronously是true,那么它必须返回一个已完成的任务。由于任务已经完成,所以在没有死锁的情况下进行阻塞是安全的。从技术上讲,“块”不是真正正确的词,因为任务已经完成;GetAwaiter().GetResult()只是从任务中提取结果(返回值或异常)。
或者,将异步方法和同步方法组合在一起做完全相同的事情的其他模式是常见的吗?
我的文章的一个更新是,ValueTask<T>现在是可用的,这意味着不需要为同步路径分配Task<T>。
此外,Stephen几周前刚刚发表了一篇博客文章,其中提到了对模式的进一步改进。有一种技巧是可以做到的;我认为它还没有名字,但我认为它是“泛型代码生成”,因为在逻辑上它类似于C++中的模板代码生成。其思想是,您有一个实现接口的值类型,通过将泛型参数约束到该接口,编译器将为每个泛型参数生成不同的代码(因为它们是值类型)。此外,由于继承的限制和缺乏,所产生的IL极有可能被JIT编译器进行良好的优化(即内联)。
用一个例子来解释可能比用词语更容易:
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();发布于 2022-09-21 20:15:58
C#方法是着色的
不幸的是,C#对于如何调用方法没有任何抽象。C#方法是彩色的,这是无法回避的。
另一种选择是只有一个async实现,而不是async和非async。在您的示例中,您将丢弃public void DoSomething(object input)方法(并将xyzInternally方法的主体移动到异步方法)。
不幸的是,这导致了一种恼人的情况,在这种情况下,您必须使用异步/等待从异步方法一直到调用堆栈。它被称为“异步癌症”,其原因是C#方法是有色的。
我可能错过了大局。如果是这样的话,那么请提供更多的例子,这样我就可以看到你看到的相同的模式。
P.S. Task.GetAwaiter().GetResult()与Task.Result本质上是一样的。是的,异常产生的方式有所不同,但两者都很容易导致死锁(以及其他问题)。例如:你能保证await GetSomethingAsync(input, lol)永远不会重新进入DoSomethingInternally吗?
发布于 2022-09-21 20:34:07
这个怎么样:
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();
}https://stackoverflow.com/questions/73806291
复制相似问题