Skip to content

错误的使用异步方法

使用List.ForEach(Async () => {await DoAsync()})会有哪些问题

  1. 异步方法不会被正确等待
    • List.ForEach 的设计是同步的,不支持异步操作。
    • 当你在 List.ForEach 中使用 async 方法时,ForEach 不会等待每个异步操作完成,而是直接返回,导致异步操作在后台运行。
    • 这种行为可能会导致未完成的任务或逻辑错误(依赖注入的 scope 被释放)。
  1. 异步异常可能无法正确捕获
    • 如果 DoAsync(item) 抛出了异常,异常可能不会被捕获,因为异步任务在 List.ForEach 返回后继续执行。
    • 如果你没有显式处理任务异常,可能会导致未观察到的任务异常(UnobservedTaskException)。
  1. 并发问题
    • List.ForEach 的设计是逐项执行,而不是并发执行。如果你希望异步任务能够并发执行,那么 List.ForEach 不适合。
    • 即使你将异步任务写入 List.ForEach,任务之间的执行顺序仍然是同步的。

解决方案

  1. 使用 Parallel.ForEachAsync(.NET 6+)
csharp
List<int> items = new List<int> { 1, 2, 3, 4, 5 };

await Parallel.ForEachAsync(items, async (item, cancellationToken) =>
{
    await DoAsync(item);
});
  1. 扩展一个新的方法
csharp
public static async Task ForEachAsync<T>(this IEnumerable<T>? source, Func<T, Task> action)
{
    if (source == null) return;
    foreach (var value in source)
    {
        await action(value);
    }
}
  1. 使用task.WhenAll
csharp
var tasks = new List<Task>();
List.ForEach(Async () => {tasks.Add(DoAsync())})
await Task.WhenAll(tasks);

controller中使用 _ = Task.Run(() => DoSomethingAsync())会有哪些风险

    • 异步方法不会被正确等待
    • Task.Run 会立即返回一个 Task 对象,而不会等待 DoSomethingAsync 完成。
    • 如果 DoSomethingAsync 中有依赖于当前请求上下文的操作(如数据库操作、HttpContext 等),这些操作可能会在请求结束后执行,导致上下文丢失。
    • 异步异常可能无法正确捕获
    • 如果 DoSomethingAsync 抛出异常,异常可能不会被捕获,因为 Task.Run 返回的 Task 不会在当前请求上下文中处理。
    • 如果没有显式处理任务异常,可能会导致未观察到的任务异常(UnobservedTaskException)。
    • 并发问题
    • Task.Run 会在后台线程池线程上执行 DoSomethingAsync,这可能导致并发执行。
    • 如果 DoSomethingAsync 中有共享资源访问,可能会导致并发问题,如数据竞争或死锁。

解决方案

  1. 在 Task中使用
csharp
public string MergeRequest([FromBody] GitLabMergeWebhookRequest hook)
{
    _ = Task.Factory.StartNew(async () =>
    {
        try
        {
            using (var scope = _scopeFactory.CreateScope())
            {
                var webhookService = scope.ServiceProvider.GetRequiredService<IWebhookService>();
                var logger = scope.ServiceProvider.GetRequiredService<SkyNetLogger<WebhookController>>();
                logger.LogInfo($"接收到数据: {JsonConvert.SerializeObject(hook)}", "webhook");
                await webhookService.HandleAsync(hook);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"接收结果异常: {JsonConvert.SerializeObject(hook)}", "webhook");
        }
    }, TaskCreationOptions.LongRunning);
    return "";
}
  1. 注入的服务使用Singleton(全局实例唯一)