为封装IDisposable对象的Task实现IDisposable/IAsyncDisposable可靠模式
优化返回IDisposable对象的Task字段的Dispose实现,以及IAsyncDisposable适配方案
核心问题分析
你的场景中,类持有一个返回IDisposable对象的Task字段(比如对接SharePoint、谷歌云盘、Dropbox时,获取API令牌的Task,令牌关联了需释放的会话、连接等资源),原Dispose实现未覆盖Task的所有状态(已完成、执行中、失败),需要确保资源被正确清理,同时支持异步释放逻辑。
同步Dispose优化实现
首先新增CancellationTokenSource管控正在执行的Task,在Dispose中处理Task的三种状态,确保资源不泄漏:
public class CloudStorageClient : IDisposable { private readonly CancellationTokenSource _cts = new(); private Task<IDisposable>? _tokenResourceTask; private bool _disposed; // 初始化时启动令牌获取Task,复用令牌避免重复认证 public CloudStorageClient() { _tokenResourceTask = AcquireTokenWithResourceAsync(_cts.Token); } // 模拟对接云API获取令牌及关联可释放资源的逻辑 private async Task<IDisposable> AcquireTokenWithResourceAsync(CancellationToken ct) { // 实际场景中:调用云API获取令牌,创建关联的可释放资源(如HTTP客户端、会话) var tokenResource = new TokenResource(); await Task.Delay(1000, ct); // 模拟API调用延迟 return tokenResource; } // 业务方法:复用已获取的令牌 public async Task ExecuteApiCallAsync() { if (_disposed) throw new ObjectDisposedException(nameof(CloudStorageClient)); var tokenResource = await _tokenResourceTask!; // 使用tokenResource中的令牌调用云API } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // 取消正在执行的令牌获取操作 _cts.Cancel(); if (_tokenResourceTask != null) { try { // 等待Task完成(无论成功、取消还是失败) _tokenResourceTask.Wait(); // 仅当Task成功完成时,释放返回的IDisposable资源 if (_tokenResourceTask.Status == TaskStatus.RanToCompletion) { _tokenResourceTask.Result.Dispose(); } } catch (AggregateException ex) { // 过滤取消异常,其他异常可按需记录日志 ex.Handle(e => e is OperationCanceledException); } _tokenResourceTask = null; } _cts.Dispose(); } _disposed = true; } // 模拟令牌关联的可释放资源类 private class TokenResource : IDisposable { public void Dispose() { // 实际逻辑:关闭HTTP连接、清理云API会话等 } } }
关键处理逻辑
- 执行中Task:通过
_cts.Cancel()终止操作,等待Task完成后,若成功则释放资源 - 已完成Task:直接释放返回的
IDisposable对象 - 已失败Task:无需释放资源(未成功获取到可释放对象),仅捕获异常避免崩溃
异步释放:实现IAsyncDisposable
如果资源支持异步释放(如.NET 6+的HttpClient异步释放、云API的异步会话关闭),需实现IAsyncDisposable接口,避免阻塞调用线程:
public class CloudStorageClient : IDisposable, IAsyncDisposable { private readonly CancellationTokenSource _cts = new(); private Task<IAsyncDisposable>? _tokenResourceTask; private bool _disposed; private bool _disposedAsync; public CloudStorageClient() { _tokenResourceTask = AcquireTokenWithAsyncResourceAsync(_cts.Token); } private async Task<IAsyncDisposable> AcquireTokenWithAsyncResourceAsync(CancellationToken ct) { var tokenResource = new AsyncTokenResource(); await Task.Delay(1000, ct); return tokenResource; } public async Task ExecuteApiCallAsync() { if (_disposed || _disposedAsync) throw new ObjectDisposedException(nameof(CloudStorageClient)); var tokenResource = await _tokenResourceTask!; // 使用令牌调用云API } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // 同步场景下调用异步释放并阻塞等待,确保资源清理 _ = DisposeAsync().AsTask().Wait(); } _disposed = true; } public async ValueTask DisposeAsync() { if (_disposedAsync) return; try { _cts.Cancel(); if (_tokenResourceTask != null) { try { await _tokenResourceTask; if (_tokenResourceTask.Status == TaskStatus.RanToCompletion) { await _tokenResourceTask.Result.DisposeAsync(); } } catch (OperationCanceledException) { // 忽略取消异常 } catch (Exception ex) { // 按需记录失败日志 } _tokenResourceTask = null; } await _cts.DisposeAsync(); } finally { _disposedAsync = true; } } // 支持异步释放的令牌资源类 private class AsyncTokenResource : IAsyncDisposable { public async ValueTask DisposeAsync() { // 实际逻辑:异步关闭HTTP连接、异步终止云API会话等 await Task.Delay(100); } } }
异步释放要点
- 优先使用
DisposeAsync()进行异步清理,避免阻塞线程 - 同步
Dispose()内部调用异步释放方法,确保同步场景下资源也能被清理 - 处理Task状态的逻辑与同步版一致,但使用异步等待和异步释放API
注意事项
- 所有业务方法需检查
_disposed/_disposedAsync状态,避免对象已释放后继续操作 - Dispose操作不应抛出异常,异常仅需按需记录日志
- 令牌复用逻辑确保
_tokenResourceTask仅初始化一次,避免重复认证
内容的提问来源于stack exchange,提问作者Brent
相关产品推荐
相关产品推荐

