¿Cómo puedo saber si un método de C # es asíncrono / espera a través de la reflexión?

p.ej

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

Si reflexionamos sobre esta clase y método, ¿cómo puedo determinar si se trata de un método real async / await en lugar de simplemente un método que devuelve una Tarea?

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

En mi copia de su código, MethodInfo para el método async contiene los siguientes elementos en la propiedad CustomAttributes :

  • un DebuggerStepThroughAttribute
  • un AsyncStateMachineAttribute

mientras que MethodInfo para el método normal no contiene elementos en su propiedad CustomAttributes .

Parece que el AsyncStateMachineAttribute debe encontrarse de manera confiable en un método async y no en uno estándar .

Edit: De hecho, ¡esa página incluso tiene lo siguiente en los ejemplos!

Como muestra el siguiente ejemplo, puede determinar si un método está marcado con modificador Async (Visual Basic) o async (Referencia de C #). En el ejemplo, IsAsyncMethod realiza los siguientes pasos:

  • Obtiene un objeto MethodInfo para el nombre del método utilizando Type.GetMethod.

  • Obtiene un objeto Tipo para el atributo utilizando el Operador GetType (Visual Basic) o typeof (Referencia de C #).

  • Obtiene un objeto de atributo para el método y el tipo de atributo utilizando MethodInfo.GetCustomAttribute. Si GetCustomAttribute devuelve Nothing (Visual Basic) o null (C #), el método no contiene el atributo.

 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); } 

Aquí hay un ejemplo de dos métodos, y le pregunto por qué cree que deberían tratarse de manera diferente:

  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); } 

Ambos tienen, dentro de una onda manual, exactamente el mismo comportamiento en tiempo de ejecución: durante 20 segundos no hacen nada (y no se aferran a un hilo durante ese período) y luego ejecutan un pequeño delegado que simplemente devuelve el valor que inicialmente era Pasado al método.

¿Y, sin embargo, vas a tratar uno muy diferente del otro porque elijo usar el comstackdor para ocultar algunas de las tuberías?

Damien_The_Unbeliever lanzó un desafío interesante. Creo que la comprobación de AsyncStateMachineAttribute no es una solución suficiente. La pregunta original no debería ser si el método es asíncrono. En su lugar, debería ser si es aguardable. Ambos ejemplos de métodos en la respuesta de Damien devolverán verdadero si comprueba el método GetAwaiter() en el tipo de devolución. Sin embargo, solo el método marcado como async incluirá el AsyncStateMachineAttribute en la colección de atributos personalizados.

Saber si el método es aguardable es importante si quiere usar MethodInfo.Invoke() para llamar al método y no sabe de antemano si los métodos que podrían estar registrados en un intermediario de mensajes, por ejemplo, están disponibles.

 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); } 

EDIT: buena idea para comprobar el tipo de retorno en MethodInfo. Este es mi código revisado.

 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); } }