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

异步代码中不立即执行任务的优雅实现方案求助

异步代码中避免使用.GetAwaiter().GetResult()的优雅实现方案

我现有代码可实现预期功能,但在异步代码中不得不使用.GetAwaiter().GetResult()这种同步阻塞方式,想请教是否存在无需此类取巧手段的优雅实现方案。

简化后的现有代码

public async Task<string[]> GetValues(int[] keys)
{
    List<int> keysNotYetActivelyRequested = null;
    // 此时不启动任务,因为keysNotYetActivelyRequested还未填充
    var taskToCreateWithoutStarting = new Task<Dictionary<int, string>>(
        () => GetValuesFromApi(keysNotYetActivelyRequested.ToArray())
        .GetAwaiter().GetResult() /*这不是好办法*/);
    (var allTasksToAwait, keysNotYetActivelyRequested) = GetAllTasksToAwait(
        keys, taskToCreateWithoutStarting);

    if (keysNotYetActivelyRequested.Any())
    {
        // 当所有键都已属于其他活跃请求时,keysNotYetActivelyRequested为空
        taskToCreateWithoutStarting.Start(TaskScheduler.Current);
    }
    var allResults = await Task.WhenAll(allTasksToAwait);
    var theReturn = new string[keys.Length];
    for (int i = 0; i < keys.Length; i++)
    {
        foreach (var result in allResults)
        {
            if (result.TryGetValue(keys[i], out var value))
            {
                theReturn[i] = value;
            }
        }
    }
    if (keysNotYetActivelyRequested.Any())
    {
        taskToCreateWithoutStarting.Dispose();
    }
    return theReturn;
}

// 按键索引所有活跃请求,避免为同一键生成多个请求
private Dictionary<int, Task<Dictionary<int, string>>> _activeRequests = new();
private (HashSet<Task<Dictionary<int, string>>> allTasksToAwait,
    List<int> keysNotYetActivelyRequested) GetAllTasksToAwait(
    int[] keys, Task<Dictionary<int, string>> taskToCreateWithoutStarting)
{
    var keysNotYetActivelyRequested = new List<int>();
    // 用HashSet是因为每个任务对应多个键,_activeRequests中会多次出现同一任务
    var allTasksToAwait = new HashSet<Task<Dictionary<int, string>>>();

    // 添加任务完成后的清理逻辑,从_activeRequests中移除请求的键
    var taskWithCleanup = taskToCreateWithoutStarting.ContinueWith(_ =>
    {
        lock (_activeRequests)
        {
            foreach (var key in keysNotYetActivelyRequested)
            {
                _activeRequests.Remove(key);
            }
        }
    });
    lock (_activeRequests)
    {
        foreach (var key in keys)
        {
            // 使用CollectionsMarshal避免同一键的两次查找
            ref var refToTask = ref CollectionsMarshal.GetValueRefOrAddDefault(
                _activeRequests, key, out var exists);
            if (exists)
            {
                allTasksToAwait.Add(refToTask);
            }
            else
            {
                refToTask = taskToCreateWithoutStarting;
                allTasksToAwait.Add(taskToCreateWithoutStarting);
                keysNotYetActivelyRequested.Add(key);
            }
        }
    }
    return (allTasksToAwait, keysNotYetActivelyRequested);
}

// 并非实际代码
private async Task<Dictionary<int, string>> GetValuesFromApi(int[] keys)
{
    // 请求时长取决于键的数量
    await Task.Delay(keys.Length);
    return keys.ToDictionary(k => k, k => k.ToString());
}

测试方法

[Test]
public void TestGetValues()
{
    var random = new Random();
    var allTasks = new Task[10];
    for (int i = 0; i < 10; i++)
    {
        var arrayofRandomInts = Enumerable.Repeat(random, random.Next(1, 100))
            .Select(r => r.Next(1, 100)).ToArray();
        allTasks[i] = GetValues(arrayofRandomInts);
    }
    Assert.DoesNotThrowAsync(() => Task.WhenAll(allTasks));
    Assert.That(_activeRequests.Count, Is.EqualTo(0));
}

优雅实现方案

原代码中使用.GetAwaiter().GetResult()会导致同步阻塞,在存在同步上下文的环境(如UI、ASP.NET)中极易引发死锁,且不符合异步编程的最佳实践。我们可以用TaskCompletionSource来重构代码,全程使用异步操作,避免同步阻塞:

修改后的代码

public async Task<string[]> GetValues(int[] keys)
{
    TaskCompletionSource<Dictionary<int, string>> tcs = null;
    (var allTasksToAwait, var keysNotYetActivelyRequested) = GetAllTasksToAwait(keys, () =>
    {
        tcs = new TaskCompletionSource<Dictionary<int, string>>(TaskCreationOptions.RunContinuationsAsynchronously);
        return tcs.Task;
    });

    if (keysNotYetActivelyRequested.Any())
    {
        // 异步执行API调用并完成TaskCompletionSource
        _ = Task.Run(async () =>
        {
            try
            {
                var result = await GetValuesFromApi(keysNotYetActivelyRequested.ToArray());
                tcs.SetResult(result);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }
        });
    }

    var allResults = await Task.WhenAll(allTasksToAwait);
    var theReturn = new string[keys.Length];
    for (int i = 0; i < keys.Length; i++)
    {
        foreach (var result in allResults)
        {
            if (result.TryGetValue(keys[i], out var value))
            {
                theReturn[i] = value;
                break; // 找到对应值后终止循环,提升效率
            }
        }
    }

    return theReturn;
}

// 按键索引所有活跃请求,避免为同一键生成多个请求
private Dictionary<int, Task<Dictionary<int, string>>> _activeRequests = new();
private (HashSet<Task<Dictionary<int, string>>> allTasksToAwait,
    List<int> keysNotYetActivelyRequested) GetAllTasksToAwait(
    int[] keys, Func<Task<Dictionary<int, string>>> createNewTask)
{
    var keysNotYetActivelyRequested = new List<int>();
    var allTasksToAwait = new HashSet<Task<Dictionary<int, string>>>();
    Task<Dictionary<int, string>> newTask = null;

    lock (_activeRequests)
    {
        foreach (var key in keys)
        {
            ref var refToTask = ref CollectionsMarshal.GetValueRefOrAddDefault(
                _activeRequests, key, out var exists);
            if (exists)
            {
                allTasksToAwait.Add(refToTask);
            }
            else
            {
                newTask ??= createNewTask();
                refToTask = newTask;
                allTasksToAwait.Add(newTask);
                keysNotYetActivelyRequested.Add(key);
            }
        }
    }

    // 任务完成后清理_activeRequests中的对应键
    newTask?.ContinueWith(task =>
    {
        lock (_activeRequests)
        {
            foreach (var key in keysNotYetActivelyRequested)
            {
                _activeRequests.Remove(key);
            }
        }
    }, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.OnlyOnFaulted);

    return (allTasksToAwait, keysNotYetActivelyRequested);
}

// 原API调用方法不变
private async Task<Dictionary<int, string>> GetValuesFromApi(int[] keys)
{
    await Task.Delay(keys.Length);
    return keys.ToDictionary(k => k, k => k.ToString());
}

关键修改点说明

  1. 用TaskCompletionSource替代手动创建未启动的Task:TaskCompletionSource允许我们手动控制任务的完成状态,无需提前创建并阻塞异步方法调用。
  2. 延迟创建任务:通过Func<Task<Dictionary<int, string>>>委托,仅在确定需要新请求时才创建TaskCompletionSource,避免不必要的资源消耗。
  3. 全程异步操作:API调用全程使用await,彻底移除同步阻塞的.GetAwaiter().GetResult(),消除死锁风险。
  4. 优化任务清理逻辑:将清理操作绑定到任务完成事件,确保_activeRequests始终保持正确状态,同时支持任务成功或失败的场景。
  5. 提升结果查找效率:找到对应键的值后立即终止循环,减少不必要的遍历。

内容的提问来源于stack exchange,提问作者Lev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 22:07:29