前言
上次,我們雖然用代碼實現了“異步 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 模式”了!
想了解更多內容,請關注我的個人公眾號”My IO“