前言:我正在研究如何最好地在 C# 中实现处理异步方法的通用装饰器。大多数示例使用
DispatcherProxy
,它仅提供同步 Invoke
。除非 NET 改进了这一点并添加了异步支持,目前已关闭,否则我们只能以与普通特定装饰器相同的方式调用它。问题似乎是因为决定让等待成为一种模式,所以没有干净的方法来检测这种情况并正确编写代码,但我希望更熟悉反射深度的人可以照亮.
先看一个具体的简单例子:
interface IMyWork
{
Task DoStuff();
}
class MyWork : IMyWork
{
public async Task DoStuff() { await Something(); }
}
// A decorator like any other
class MyDecoratedWork : IMyWork
{
private readonly MyWork _real;
public Task DoStuff()
{
using var scope = Decorate();
await _real.Something();
// Disposal is in the same context as Decorate was - things like AsyncLocal are restored
}
private IDisposable Decorate() { /* return some scoped decoration /* }
}
如果您想手动编写装饰器,这很容易,但如果您有很多要编写的装饰器,那么拥有一个通用装饰器就变得很有吸引力。示例往往如下所示:
class DispatchProxyScopedDecorator<TDecorated> : DispatchProxy
{
private TDecorated? _decorated;
private IScopeProvider? _scopeProvider;
private void SetParameters(TDecorated decorated, IScopeProviderscopeProvider)
{
_decorated = decorated;
_scopeProvider = scopeProvider;
}
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
// The important bit
}
public static TDecorated Create(TDecorated decorated, IScopeProvider scopeProvider)
{
object created = Create<TDecorated, DispatchProxyScopedDecorator<TDecorated>>()!;
((DispatchProxyScopedDecorator<TDecorated>)created).SetParameters(decorated, scopeProvider);
return (TDecorated)created;
}
}
同步情况很简单:
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
using var scope = _scopeProvider.Decorate();
return targetMethod?.Invoke(_decorated, args);
}
但是
Invoke
是一个同步方法,这意味着要正确修饰返回awaitable的方法调用,必须用新的awaitable拦截返回类型。
唯一的要求似乎是实现一个方法
GetAwaiter
(像AsyncStateMachineAttribute
之类的东西不一定适用)。
var scope = _scopeProvider.Decorate();
var result = targetMethod.Invoke(_decorated, args);
if (result is null)
{
return null;
}
// This isn't complete - also needs to hunt through assemblies for extension methods!
var awaiterMethod = result.GetType().GetMethod("GetAwaiter");
如果这不是
null
,我还有一个等待。就易于维护的代码而言,我的理想是将其放入异步方法中,然后让编译器完成艰苦的工作:
async Task InterceptAsync(??? awaitable, MyScope scope)
{
try
{
await awaitable;
}
finally
{
scope.Dispose();
}
}
if (awaiterMethod is not null)
{
return InterceptAsync(result, scope);
}
不幸的是,由于明显的原因,这不能编译 - 因为可等待不是单一类型,所以我无法向
???
提供编译器会接受的任何内容(除了 dynamic
之外,但这已被弃用)。
任何装饰器都有明显的缺点(性能、返回类型的潜在变化),但这是否可行?我是否缺少其他方法?我的直接想法是,如果原始代码没有返回
Task
,则上述内容将违反预期的方法签名 - 但这是否意味着每个不同的可等待对象都需要自己的实现?
感谢您的想法。
如果您只想在
Dispose
之后拨打 await
,也许您可以只检查强制性 INotifyCompletion/ICriticalNotifyCompletion
,然后在那里注册 scope.Dispose()
。
类似这样的:
var awaiter = awaiterMethod.Invoke()
if(awaiter is INotifyCompletion notifycompl){
notifycomp.OnCompleted(()=>scope.Dispose());
}
return result;