You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:02:46