如何在C#中计算异步方法总执行时间(无法使用Task)
解决Async Void批量执行后的等待问题
嘿,这个场景我太熟了——async void 方法因为不会返回Task,根本没法直接跟踪它的完成状态,这也是官方不推荐在非事件处理场景用它的核心原因之一。既然你说没法修改方法返回Task,那咱们用线程安全的计数器+信号量的方案来搞定,绝对能让Stopwatch等所有异步任务都跑完再计时:
推荐方案:计数器+ManualResetEventSlim
思路很简单:先初始化一个和任务数量一致的计数器,每次DoSomethingAsync执行完(哪怕抛出异常)就把计数器减1,当计数器降到0时,就给主线程发信号,让Stopwatch停止计时。
完整代码示例
using System; using System.Threading; using System.Diagnostics; class Program { // 线程安全的计数器,记录还在跑的任务数 private static int _pendingTasks; // 用来通知主线程所有任务完成的信号量 private static ManualResetEventSlim _allTasksDone = new ManualResetEventSlim(false); private async static void DoSomethingAsync(int i) { try { // 这里替换成你实际的异步逻辑,比如调用API、读写文件啥的 await Task.Delay(100); // 模拟异步操作 Console.WriteLine($"任务 {i} 执行完成"); } finally { // 线程安全地递减计数器,确保多线程下不会计数混乱 if (Interlocked.Decrement(ref _pendingTasks) == 0) { // 所有任务都完成了,触发信号 _allTasksDone.Set(); } } } static void Main(string[] args) { Stopwatch stopwatch = Stopwatch.StartNew(); // 初始化任务数 _pendingTasks = 50; for (int i = 0; i < 50; i++) { DoSomethingAsync(i); } // 阻塞主线程,直到所有任务完成 _allTasksDone.Wait(); stopwatch.Stop(); Console.WriteLine($"总处理时间:{stopwatch.ElapsedMilliseconds} 毫秒"); } }
关键细节说明
Interlocked.Decrement是线程安全的操作,避免多个异步任务同时修改计数器导致的错误- 把计数器递减放在
finally块里,哪怕某个任务抛出异常,也能保证计数器正确递减,不会让主线程一直等下去 ManualResetEventSlim比老的ManualResetEvent更轻量,性能更好,适合这种短时间等待的场景
备选方案(不推荐,有局限)
如果你想尝试用Task相关的方式间接包装,但注意这个方案有坑:因为async void的内部异步操作没法被跟踪,包装出来的Task其实只能跟踪到方法调用完成,而不是异步逻辑完成。代码大概是这样:
using System; using System.Threading.Tasks; using System.Diagnostics; class Program { private async static void DoSomethingAsync(int i) { await Task.Delay(100); Console.WriteLine($"任务 {i} 执行完成"); } // 把async void包装成Task(但只能跟踪调用,不能跟踪内部异步) private static Task WrapAsyncMethod(int i) { var tcs = new TaskCompletionSource<bool>(); try { async void Wrapper() { try { DoSomethingAsync(i); tcs.SetResult(true); } catch (Exception ex) { tcs.SetException(ex); } } Wrapper(); } catch (Exception ex) { tcs.SetException(ex); } return tcs.Task; } static void Main(string[] args) { Stopwatch stopwatch = Stopwatch.StartNew(); var tasks = new Task[50]; for (int i = 0; i < 50; i++) { tasks[i] = WrapAsyncMethod(i); } Task.WaitAll(tasks); stopwatch.Stop(); Console.WriteLine($"总处理时间:{stopwatch.ElapsedMilliseconds} 毫秒"); } }
这个方案的问题在于:Task.WaitAll等的是Wrapper方法执行完,而不是DoSomethingAsync内部的异步逻辑完成,所以计时结果还是不准,不建议用。
最后提一句
如果以后有机会,尽量把async void改成async Task,这样代码会简洁太多:
// 修改方法返回Task private async static Task DoSomethingAsync(int i) { await Task.Delay(100); Console.WriteLine($"任务 {i} 执行完成"); } // 调用时直接用Task.WhenAll等待 var tasks = Enumerable.Range(0, 50).Select(i => DoSomethingAsync(i)).ToArray(); await Task.WhenAll(tasks); // 异步上下文用这个 // 同步上下文用 Task.WaitAll(tasks);
内容的提问来源于stack exchange,提问作者ahsant
相关产品推荐
相关产品推荐

