如何通过reflection判断C#方法是否异步/等待?

例如

class Foo { public async Task Bar() { await Task.Delay(500); } } 

如果我们反思这个类和方法,我如何确定这是否是一个真正的async / await方法,而不仅仅是一个碰巧返回任务的方法?

 class Foo { public Task Bar() { return Task.Delay(500); } } 

在我的代码副本中, async方法的MethodInfo包含CustomAttributes属性中的以下项:

  • DebuggerStepThroughAttribute
  • AsyncStateMachineAttribute

而普通方法的MethodInfo在其CustomAttributes属性中包含任何项目。

似乎AsyncStateMachineAttribute 应该可靠地async方法上找到而不是在标准方法上找到 。

编辑:事实上,该页面甚至在示例中有以下内容!

如以下示例所示,您可以确定方法是使用Async(Visual Basic)还是async(C#Reference)修饰符进行标记。 在该示例中,IsAsyncMethod执行以下步骤:

  • 使用Type.GetMethod获取方法名称的MethodInfo对象。

  • 通过使用GetType运算符(Visual Basic)或typeof(C#引用)获取属性的Type对象。

  • 使用MethodInfo.GetCustomAttribute获取方法和属性类型的属性对象。 如果GetCustomAttribute返回Nothing(Visual Basic)或null(C#),则该方法不包含该属性。

 private static bool IsAsyncMethod(Type classType, string methodName) { // Obtain the method with the specified name. MethodInfo method = classType.GetMethod(methodName); Type attType = typeof(AsyncStateMachineAttribute); // Obtain the custom attribute for the method. // The value returned contains the StateMachineType property. // Null is returned if the attribute isn't present for the method. var attrib = (AsyncStateMachineAttribute)method.GetCustomAttribute(attType); return (attrib != null); } 

这是两个方法的示例,我问你为什么你认为应该区别对待它们:

  public static async Task M1(int value) { await Task.Delay(20000); return value; } public static Task M2(int value) { return Task.Delay(20000).ContinueWith(_=>value); } 

在手动波中,它们都具有完全相同的运行时行为 – 在20秒内它们什么也不做(并且在此期间不保留线程)然后它们运行一个小代理,它只传回最初的值传递给方法。

然而,你会对待一个与另一个完全不同的因为我选择使用编译器来隐藏一些管道?

Damien_The_Unbeliever提出了一个有趣的挑战。 我认为检查AsyncStateMachineAttribute不是一个充分的解决方案。 最初的问题不应该是该方法是否异步。 相反应该是它是否可以等待。 如果您在返回类型上检查方法GetAwaiter() ,Damien的答案中的两个方法示例都将返回true。 但是,只有标记为async的方法AsyncStateMachineAttribute在自定义属性集合中包含AsyncStateMachineAttribute

如果您想使用MethodInfo.Invoke()来调用该方法,并且您可能提前知道是否可能需要注册到消息代理的方法,则知道该方法是否可以接受是很重要的。

 var isAwaitable = _methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null; object result = null; if (isAwaitable) { result = await (dynamic)_methodInfo.Invoke(_instance, _parameterArray); } else { result = _methodInfo.Invoke(_instance, _parameterArray); } 

编辑:好主意检查MethodInfo上的返回类型。 这是我修改后的代码。

 var isAwaitable = _methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null; object invokeResult = null; if (isAwaitable) { if (_methodInfo.ReturnType.IsGenericType) { invokeResult = (object)await (dynamic)_methodInfo.Invoke(_instance, arguments); } else { await (Task)_methodInfo.Invoke(_instance, arguments); } } else { if (_methodInfo.ReturnType == typeof(void)) { _methodInfo.Invoke(_instance, arguments); } else { invokeResult = _methodInfo.Invoke(_instance, arguments); } }