通用装饰器 - 正确等待从 Reflection 获得的可等待类型

问题描述 投票:0回答:1

前言:我正在研究如何最好地在 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
,则上述内容将违反预期的方法签名 - 但这是否意味着每个不同的可等待对象都需要自己的实现?

感谢您的想法。

c# asynchronous async-await system.reflection
1个回答
0
投票

如果您只想在

Dispose
之后拨打
await
,也许您可以只检查强制性
INotifyCompletion/ICriticalNotifyCompletion
,然后在那里注册
scope.Dispose()

类似这样的:

var awaiter = awaiterMethod.Invoke()
if(awaiter is INotifyCompletion notifycompl){
   notifycomp.OnCompleted(()=>scope.Dispose());
}

return result;
© www.soinside.com 2019 - 2024. All rights reserved.