Async异常未捕获:Windows Service中try..catch无法捕获RefreshTokenException
问题分析与解决方案
你遇到的核心问题是**async void方法的异常无法被调用方的try/catch捕获**,这是异步编程里很容易踩的一个坑,我来给你拆解清楚:
为什么捕获不到异常?
在.NET的异步编程模型里,async void和async Task/async Task<T>的异常处理逻辑完全不同:
- 用
async Task时,方法抛出的异常会被包装在返回的Task对象中,调用方通过await就能正常捕获这些异常; - 但
async void是专门为传统事件处理场景设计的,它抛出的异常不会回传到调用方,而是直接抛到当前线程的同步上下文(SynchronizationContext)里。在Windows Service这种没有UI同步上下文的环境中,这些异常会直接终止进程,根本不会进入你写的try/catch块。
看你的代码就能发现问题:
RefreshTokens是async void方法,里面抛出的RefreshTokenException不会被调用方Process的try/catch捕获;- 更糟的是
Process本身也是async void,它自己的异常也无法被上层代码捕获。
修复步骤
1. 把所有async void改成async Task
首先修改RefreshTokens的方法签名:
private async Task RefreshTokens() { try { var cognito = new CognitoApi(); var response = cognito.TokenRefresh(_refreshToken); if (response.HttpStatusCode == HttpStatusCode.OK) { _idToken = new AwsToken(response.AuthenticationResult.IdToken); _accessToken = new AwsToken(response.AuthenticationResult.AccessToken); } else { await _signIn(_credentials.SiteId, _credentials.LocationId, null); } } catch (NotAuthorizedException) { await _signIn(_credentials.SiteId, _credentials.LocationId, null); } catch (Exception ex) { throw new RefreshTokenException("Failed refreshing tokens.", ex); } }
然后修改Process的签名,并且必须用await调用RefreshTokens:
public async Task Process(QueueMessage queueMessage, Action<QueueMessage> retryAction) { _processingCounter.Increment(); try { // ...... IAwsToken idToken = authenticationService.Tokens.IdToken; // 这里一定要加await,才能捕获到RefreshTokens里的异常 await RefreshTokens(); // ........ } catch (Exception ex) { // 现在就能正常捕获到RefreshTokenException了 _logger.Error("Error in ProcessMessage", ex); } _processingCounter.Decrement(); }
2. 在Windows Service中正确调用异步方法
如果Process是被Windows Service的启动逻辑或定时任务调用,要注意调用方的异步处理:
比如在OnStart这种同步方法里,不能直接await,可以用Task.Run包裹,同时要捕获内部异常避免进程崩溃:
protected override void OnStart(string[] args) { _ = Task.Run(async () => { try { await Process(queueMessage, retryAction); } catch (Exception ex) { _logger.Error("Unhandled exception in service", ex); } }); }
关键总结
- 非事件处理场景下,永远不要用
async void,用async Task代替; - 所有异步方法调用必须加
await,这样异常才能被上层的try/catch捕获; - Windows Service这类无UI环境中,一定要格外注意异步方法的异常处理,避免未捕获异常导致进程意外终止。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

