异步代码中不立即执行任务的优雅实现方案求助
异步代码中避免使用
.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()); }
关键修改点说明
- 用
TaskCompletionSource替代手动创建未启动的Task:TaskCompletionSource允许我们手动控制任务的完成状态,无需提前创建并阻塞异步方法调用。 - 延迟创建任务:通过
Func<Task<Dictionary<int, string>>>委托,仅在确定需要新请求时才创建TaskCompletionSource,避免不必要的资源消耗。 - 全程异步操作:API调用全程使用
await,彻底移除同步阻塞的.GetAwaiter().GetResult(),消除死锁风险。 - 优化任务清理逻辑:将清理操作绑定到任务完成事件,确保
_activeRequests始终保持正确状态,同时支持任务成功或失败的场景。 - 提升结果查找效率:找到对应键的值后立即终止循环,减少不必要的遍历。
内容的提问来源于stack exchange,提问作者Lev
相关产品推荐
相关产品推荐

