56

e.g.

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

If we are reflecting over this class and method, how can I determine if this is an actual async/await method rather than simply a method that happens to return a Task?

class Foo { public Task Bar() { return Task.Delay(500); } }
Isaac Abraham
  • 3,422
  • 2
  • 23
  • 26
  • At the end of the day, why is it important to you? `async` is an implementation detail of the method, and should be changeable without any consumers caring. – Damien_The_Unbeliever Dec 03 '13 at 13:11
  • Because I'm writing an IoC interceptor which is trying to track the start and end of a method call. – Isaac Abraham Dec 03 '13 at 13:28
  • 4
    But if you consider that an `async` method isn't really "complete" until the task that it returns completes, why should you not also treat *any* method that returns a `Task` as incomplete until the task completes? Consider also that a non-async method returning `Task` may do a few simple things itself and then defer the bulk of its work to an `async` internal method, and just pass back to its caller the `Task` created by that method - are you planning to try to detect such a situation? – Damien_The_Unbeliever Dec 03 '13 at 13:46
  • 1
    The whole point is that I want to distinguish between such methods. A method's job may be to create a Task and return that back out to the call, as opposed to some method who is performing some work and doing an await inside it. – Isaac Abraham Dec 04 '13 at 10:58
  • But the whole point I was trying to make is that this is an *arbitrary* distinction that you're drawing. Two classes can implement the same interface. One may choose to use `async`, the other may choose to not do so. Both are fulfilling the same contract, so why should they be treated differently? And, 6 months later, either or both of those classes may have had their implementations changed and added or removed the `async` keyword - but it doesn't change the contractual behaviour that they're providing. – Damien_The_Unbeliever Dec 04 '13 at 18:10
  • It does change the contract - with an async method, you don't have to explicitly return Task, so there's an understanding there that with an Async method you're having something happen "implicitly". The other one you're explicitly returning a Task - to my mind the contract of that method is "Create a Task, start it, and return it back out". – Isaac Abraham Dec 04 '13 at 20:26
  • An interface method *cannot* be decorated with `async`. And yet implementers can choose whether or not their implementation is `async` or not - they're both fulfilling the *same* contract (and the contract *cannot* include the `async` modifier). That's why, all along, I've been saying that it's an *implemenetation* detail - it shouldn't matter to any external code. – Damien_The_Unbeliever Dec 05 '13 at 07:51
  • Except we're marking the concrete method with the attribute for logging purposes, not any interface. Let's agree to disagree here. – Isaac Abraham Dec 06 '13 at 14:18

3 Answers3

66

In my copy of your code, the MethodInfo for the async method contains the following items in the CustomAttributes property:

  • a DebuggerStepThroughAttribute
  • a AsyncStateMachineAttribute

whereas the MethodInfo for the normal method contains no items in its CustomAttributes property.

It seems like the AsyncStateMachineAttribute should reliably be found on an async method and not on a standard one.

Edit: In fact, that page even has the following in the examples!

As the following example shows, you can determine whether a method is marked with Async (Visual Basic) or async (C# Reference) modifier. In the example, IsAsyncMethod performs the following steps:

  • Obtains a MethodInfo object for the method name by using Type.GetMethod.

  • Obtains a Type object for the attribute by using GetType Operator (Visual Basic) or typeof (C# Reference).

  • Obtains an attribute object for the method and attribute type by using MethodInfo.GetCustomAttribute. If GetCustomAttribute returns Nothing (Visual Basic) or null (C#), the method doesn't contain the attribute.

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);
}
Community
  • 1
  • 1
Rawling
  • 49,248
  • 7
  • 89
  • 127
  • Beauty, thanks. I thought I had checked custom attributes but I had checked those of the Task that was returned rather than the MethodInfo - doh! – Isaac Abraham Dec 03 '13 at 13:27
  • uhm.. if method.GetCustomAttribute(attType) returns null, casting it will yield an exception. thus, the attrib != null is redundant. Use 'as'? Or even better, use GetCustomAttribute() – HelloWorld Aug 31 '15 at 08:03
  • @EmilMüller Casting `null` won't throw, although you're right that it's not necessary. It's just tidy to cast it to the type you've just asked for. The generic version didn't exist at the time of this answer. – Rawling Sep 01 '15 at 07:23
  • Are you sure this is also the case for methods that return a Task, i.e. are awaitable, but not async? Like `Task Func2() => Func1();` style redirects. – ygoe Jun 24 '18 at 18:02
  • 1
    @ygoe no. That's exactly what the asker wants to be able to distinguish. – Rawling Jun 24 '18 at 20:39
  • @Rawling What if you are reflecting from an interface? – Rodrigo Reis Feb 18 '22 at 14:04
33

Damien_The_Unbeliever threw an interesting challenge. I think checking for AsyncStateMachineAttribute isn't a sufficient solution. The original question shouldn't be whether the method is async. Instead it should be whether it's awaitable. Both method samples in Damien's answer will return true if you check for the method GetAwaiter() on the return type. However, only the method marked async will include the AsyncStateMachineAttribute in the custom attributes collection.

Knowing if the method is awaitable is important if you want to use MethodInfo.Invoke() to call the method and you don't know ahead of time if methods that might be registered to a message broker are awaitable.

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: Good idea to check the return type on MethodInfo. This is my revised code.

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);
    }
}
Mark Lauter
  • 810
  • 9
  • 22
  • 5
    Though this isn't exactly what was being asked, this is a very good point. It's typically more useful to know if the result is awaitable not whether the method is marked as `async`. – pushkin Feb 14 '19 at 15:19
  • I think I see your point: Since await can be called on anything returning a Task; not just async methods, then when creating a tool to detect such behavior, one need not know if the method is async or not (unless that is the sole goal of the tool, for something like a static code analysis rule). – ryanwebjackson Sep 23 '21 at 21:40
  • However, this is exactly not what OP asked. – ryanwebjackson Sep 23 '21 at 21:41
  • 1
    It's not exactly what they asked, but I think it's more useful. – Mark Lauter Sep 29 '21 at 23:56
10

Here's an example of two methods, and I'm asking you why you think that they should be treated differently:

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

They both have, to within a handwave, the exact same runtime behaviour - for 20 seconds they do nothing (and don't hold onto a thread during that period) and then they run a small delegate that just passes back the value that was initially passed to the method.

And yet, you're going to treat one vastly differently from the other because I choose to use the compiler to hide some of the plumbing?

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • Does [this](http://stackoverflow.com/a/17000119/215380) count as different behaviour? (Although apparently if it is, you can still [get around it](http://stackoverflow.com/a/8767406/215380) and get "identical" behaviour to `async`.) – Rawling Dec 05 '13 at 08:26
  • @Rawling - yes, i did consider adding something to configure the await, but that's why I left it as "within a handwave" for now - and, of course, via reflection (the tool the OP is using) there's no way to know what the `async` method is doing in terms of schedulers, unless you want to decompile the actual code of the method. – Damien_The_Unbeliever Dec 05 '13 at 09:25
  • Ah OK, I missed the handwave :) – Rawling Dec 05 '13 at 10:26