异步await使用问题:代码提前退出未执行全部逻辑求助
问题描述
我正在排查代码提前退出、未执行全部逻辑的问题,推测是async/await使用有误但无法定位。相关代码及日志如下:
主方法
{ Logger.Info("Monitoring Started"); var monitorer = new Monitorer(); monitorer.ScheduleService(); Logger.Info("Done"); }
Monitorer类代码
public class Monitorer { private Logger Logger { get; } = LogManager.GetCurrentClassLogger(); public async void ScheduleService() { Logger.Trace(nameof(ScheduleService)); // 实例化TaskEvaluationRuntime var runtime = new TaskEvaluationRuntime(); var s1 = new ScheduleRule() .AtMinutes(30) .WithName("CheckoutTasks") .Execute((_, _) => { new Monitoring.UpdateCheckoutDate().Run(); return true; }); var s2 = new ScheduleRule() .AtMinutes(30) .WithName("CheckoutTasks") .Execute((_, _) => { new Monitoring.UpdateOrderTag().Run(); return true; }); // 将定时任务添加到运行时 runtime.AddSchedule(s1); runtime.AddSchedule(s2); // 等待运行时启动 Logger.Trace("Await the runtime."); await runtime.RunAsync(); // 监听退出信号 Logger.Trace("Listen for some signal to quit."); Thread.Sleep(30000); // 优雅停机:请求停止并等待任务完成 Logger.Trace("Graceful shutdown. Request a stop and await running tasks."); await runtime.StopAsync(); } }
使用NuGet包Task Scheduler Engine创建定时任务s1和s2,控制台输出日志:
2023-02-27 14:58:21.8067 2023-02-27 14:58:21.8304 Monitoring Started 2023-02-27 14:58:21.9111 ScheduleService 2023-02-27 14:58:21.9111 Await the runtime. 2023-02-27 14:58:21.9195 Done
问题:为何Logger.Trace("Listen for some signal to quit.");和Logger.Trace("Graceful shutdown. Request a stop and await running tasks.");未执行?
问题原因及解决方案
核心问题1:async void导致调用方无法等待异步操作完成
ScheduleService方法声明为async void,这种写法仅适用于事件处理程序,普通异步方法应该使用async Task作为返回类型。当主方法调用monitorer.ScheduleService();时,会直接继续执行后续代码(Logger.Info("Done")),不会等待ScheduleService内部的异步逻辑完成。如果主线程是程序的唯一前台线程,主线程执行完毕后程序就会退出,导致ScheduleService中await runtime.RunAsync()之后的代码根本没机会执行。
核心问题2:runtime.RunAsync()的异步行为加剧了问题
从日志时间线看,Await the runtime.之后很快就输出了Done,说明await runtime.RunAsync()没有阻塞等待,而是立即进入异步流程。结合async void的问题,主线程直接结束,整个程序退出,后续的监听退出信号、优雅停机代码自然无法执行。
修复步骤
- 修改
ScheduleService的返回类型为async Task:
public async Task ScheduleService() { // 原有逻辑保持不变 }
- 主方法中等待异步操作完成:
如果主方法是异步方法,直接使用await:
{ Logger.Info("Monitoring Started"); var monitorer = new Monitorer(); await monitorer.ScheduleService(); Logger.Info("Done"); }
如果是控制台程序的Main方法(.NET Core/.NET 5+支持异步Main),修改为:
public static async Task Main(string[] args) { Logger.Info("Monitoring Started"); var monitorer = new Monitorer(); await monitorer.ScheduleService(); Logger.Info("Done"); }
- 验证
runtime.RunAsync()的行为:
确认TaskEvaluationRuntime.RunAsync()是启动后台任务后立即返回,还是需要等待到运行时停止。通过上述修改让主线程等待异步操作完成,就能保证后续的监听退出信号、优雅停机代码得以执行。
内容的提问来源于stack exchange,提问作者Bastien Vandamme

