前言
上次,我们虽然用代码实现了“异步 Request-Reply 模式”,但是需要为每一个长时间操作 API 实现一个对应的 AsyncXXX 操作。
其实,可以尝试用 Source Generators 减少这种重复性劳动。.
实现思路
- 
	
Controller 类必须是
partial,这样才能为它额外增加新方法; - 
	
为每个长时间操作 API 声明一个 AsyncMethodAttribute,这样 Source Generators 才知道为谁实现对应的同步操作;
 - 
	
遍历所有声明了 AsyncMethodAttribute 的方法,为其编写实现方法。
 
具体代码
1.添加 AsyncMethodAttribute
向待编译项目加入 AsyncMethodAttribute 代码:
const string asyncMethodAttributeText = @"using System;
namespace AsyncMethodGenerator
{
    public sealed class AsyncMethodAttribute : Attribute
    {
        public AsyncMethodAttribute()
        {
        }
    }
}
";
context.AddSource("AsyncMethodAttribute", SourceText.From(asyncMethodAttributeText, Encoding.UTF8));
2.遍历 AsyncMethodAttribute 声明方法
找到声明了 AsyncMethodAttribute 的所有方法:
private string GenerateMethods(SyntaxList<MemberDeclarationSyntax> members)
{
    StringBuilder stringBuilder = new StringBuilder();
    foreach (var member in members)
    {
        if(member is MethodDeclarationSyntax method && HasAsyncMethodAttribute(method))
        {
            stringBuilder.Append(GenerateAsyncMethod(method));
        }
    }
    
    return stringBuilder.ToString();
}
private bool HasAsyncMethodAttribute(MethodDeclarationSyntax method)
{
    var hasAttribute = false;
    foreach (var attributeList in method.AttributeLists)
    {
        foreach (var attribute in attributeList.Attributes)
        {
            if (attribute.Name.ToString().Equals("AsyncMethod"))
            {
                hasAttribute = true;
            }
        }
    }
    return hasAttribute;
}
3.生成 AsyncMethod 代码
根据原方法定义,生成 AsyncMethod 方法:
private string GenerateAsyncMethod(MethodDeclarationSyntax method)
{
    var stringBuilder = new StringBuilder();
    foreach (var attributeList in method.AttributeLists)
    {
        foreach (var attribute in attributeList.Attributes)
        {
            if (attribute.Name.ToString().Equals("Route"))
            {
                stringBuilder.Append($@"[Route(""async/{attribute.ArgumentList.Arguments[0].ToString().Trim('"')}"")]");
            }
            else
            {
                stringBuilder.Append($"[{attribute}]");
                stringBuilder.Append("\r\n");
            }
        }
    }
    stringBuilder.Append($"public async Task<IActionResult> Async{method.Identifier} ");
    stringBuilder.Append($"({method.ParameterList.Parameters})");
    stringBuilder.Append("\r\n");
    stringBuilder.Append($@"
        {{
            string id = Guid.NewGuid().ToString();
            string responseValue =  $@""/status/{{id}}"";
            _cache.SetString(id, responseValue);
            Task.Factory.StartNew(() =>
            {{
                var result = {GenerateCallingMethod(method)}.Result;
                _cache.SetString(id + ""_result"", JsonConvert.SerializeObject(result));
            }});
            return Accepted(responseValue);
        }}
    ");
    return stringBuilder.ToString();
}
4.使用
现在,就可以在目标项目中使用 AsyncMethod 方法了:
[ApiController]
[Route("[controller]")]
public partial class WeatherForecastController : ControllerBase
{
    ...
    [HttpGet]
    [Route("get")]
    [AsyncMethod]
    public async Task<IEnumerable<WeatherForecast>> Get()
    {
        ...
    }
}
注意 WeatherForecastController 是 partial class
结论
有了 Source Generators,可以让编译器帮我们自动实现“异步 Request-Reply 模式”了!