如何判定一个类型支不支持 await 异步等待(代码)

最近由于一些业务上的需求,需要在 OnActionExcutionAsync 和 OnActionExcuted 中判断当前请求的接口是否是异步的接口,刚好前几天看过吕毅大佬的文章《.NET 中什么样的类是可使用 await 异步等待的?》,遂封装实现一下判断类型是否为可等待类型的方法。

理论

总结起来,要想使一个方法可被 await 等待,必须具备以下条件:

  1. 这个方法返回一个类 A 的实例,这个类 A 必须满足后面的条件。
  2. 此类 A 有一个可被访问到的 GetAwaiter 方法(扩展方法也行,这算是黑科技吗?),方法返回类 B 的实例,这个类 B 必须满足后面的条件;
  3. 此类 B 实现 INotifyCompletion 接口,且拥有 bool IsCompleted { get; }属性、GetResult() 方法、void OnCompleted(Action continuation)方法。

第三点中,OnCompleted方法本身就是 INotifyCompletion 接口要求实现的,所以只需要校验前三点即可。

实践

按照要求封装以下扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.Linq;
using System.Reflection;

namespace System
{
public static class ReflectionExtension
{
public static bool IsAsyncType(this Type type)
{
var awaiter = type.GetMethod("GetAwaiter");
if (awaiter == null)
return false;
var retType = awaiter.ReturnType;
//.NET Core 1.1及以下版本中没有 GetInterface 方法,为了兼容性使用 GetInterfaces
if (retType.GetInterfaces().All(i => i.Name != "INotifyCompletion"))
return false;
if (retType.GetProperty("IsCompleted") == null)
return false;
if (retType.GetMethod("GetResult") == null)
return false;
return true;
}
}
}

借用大佬博客中的测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Program
{
static void Main(string[] args)
{
Console.WriteLine(typeof(Test).IsAsyncType());
Console.ReadLine();
}
}

public class Test
{
public Test2 GetAwaiter()
{
return new Test2();
}
}

public class Test2 : INotifyCompletion
{
public bool IsCompleted { get; }
public void GetResult() { }
public void OnCompleted(Action continuation) { }
}

OnActionExcutionAsyncOnActionExcuted 中的使用

OnActionExcutionAsyncOnActionExcuted 中使用时,可以再封装一层扩展方法,方便直接使用事件中的上下文实例调用。

1
2
public static bool IsAsyncAction(this FilterContext context) 
=> ((ControllerActionDescriptor) context.ActionDescriptor).MethodInfo.ReturnType.IsAsyncType();

调用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.IsAsyncAction())
Console.WriteLine("Current action is support async.");
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.IsAsyncAction())
Console.WriteLine("Current action is support async.");
}
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.IsAsyncAction())
Console.WriteLine("Current action is support async.");
return base.OnActionExecutionAsync(context, next);
}

以上↑