Как я могу определить, является ли метод С# асинхронным/ожидающим через отражение?
например.
class Foo { public async Task Bar() { await Task.Delay(500); } }
Если мы размышляем над этим классом и методом, как я могу определить, является ли это реальным методом async/await, а не просто методом, который возвращает задачу?
class Foo { public Task Bar() { return Task.Delay(500); } }
Ответы
Ответ 1
В моей копии вашего кода MethodInfo
для метода async
содержит следующие элементы в свойстве CustomAttributes
:
DebuggerStepThroughAttribute
AsyncStateMachineAttribute
тогда как MethodInfo
для обычного метода не содержит элементов в своем свойстве CustomAttributes
.
Похоже, что AsyncStateMachineAttribute
должен быть надежно найден методом async
, а не стандартным.
Изменение: На самом деле эта страница даже содержит следующее в примерах!
Как показано в следующем примере, вы можете определить, помечен ли метод модификатором Async (Visual Basic) или async (С# Reference). В этом примере IsAsyncMethod выполняет следующие шаги:
Получает объект MethodInfo для имени метода с помощью Type.GetMethod.
Получает объект Type для атрибута с помощью оператора GetType (Visual Basic) или typeof (справочник по С#).
Получает объект атрибута для метода и типа атрибута с помощью MethodInfo.GetCustomAttribute. Если GetCustomAttribute возвращает Nothing (Visual Basic) или null (С#), метод не содержит атрибут.
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);
}
Ответ 2
Damien_The_Unbeliever бросил интересный вызов. Я думаю, что проверка на AsyncStateMachineAttribute
не является достаточным решением. Первоначальный вопрос не должен заключаться в том, является ли метод асинхронным. Вместо этого должно быть ли это ожидаемым. Оба примера метода в ответе Дэмьена вернут true, если вы проверите метод GetAwaiter()
в типе возврата. Однако только метод, помеченный async
, будет включать 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);
}
EDIT:
Хорошая идея проверить тип возвращаемого значения на 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);
}
}
Ответ 3
Вот пример двух методов, и я спрашиваю вас, почему вы думаете, что с ними нужно обращаться иначе:
public static async Task<int> M1(int value)
{
await Task.Delay(20000);
return value;
}
public static Task<int> M2(int value)
{
return Task.Delay(20000).ContinueWith<int>(_=>value);
}
Оба они имеют в ручном режиме то же самое поведение во время выполнения - за 20 секунд они ничего не делают (и не удерживают нить в течение этого периода), а затем они запускают небольшой делегат, который просто передает значение который был первоначально передан методу.
И все-таки вы будете относиться к одному совершенно по-другому от другого, потому что я предпочитаю использовать компилятор, чтобы скрыть некоторые из сантехники?