ValueTask 不可重复 await,否则抛 InvalidOperationException;它是一次性资源,设计目标是零分配,而 Task 可安全多次 await;需多次使用时应转为 Task 或提取结果值。
直接 await 同一个 ValueTask 实例两次,运行时大概率会触发异常:System.InvalidOperationException: "The ValueTask may only be awaited once."。这不是未定义行为,而是 .NET 在 ValueTask 内部做了明确检查 —— 它不是设计来支持重复消费的。
ManualResetValueTaskSourceCore 或类似机制实现时,首次 await 会标记“已获取”,再次 await 就直接 throwValueTask 包装的是已完成的 Task(比如 ValueTask.FromResult(42)),也仍受此限制 —— 因为它内部可能持有一个可重用的 Task,但 ValueTask 本身仍是单次语义ValueTask)可能不抛异常,但这是实现细节,不可依赖Task 可以安全地多次 await:它本身是“热”的、可共享的;而 ValueTask 是“冷”的、一次性资源,设计目标是避免堆分配,代价就是放弃可重用性。
await task; + await task; → 正常,第二次 await 立即返回结果var vt = new ValueTask(42); await vt; await vt; → 第二次 await 抛异常vt.AsTask() 得到一个可重用的 Task,但会触发一次堆分配(失去 ValueTask 的零分配优势)核心原则:不要保存 ValueTask 变量后反复 await;要么转成 Task,要么把结果提取出来再复用。
await,再存结果值 —— int result = await GetValueAsync(); // ValueTask
Console.WriteLine(result);
Console.WriteLine(result * 2);
ValueTask 实例 —— for (int i = 0; i < 3; i++) {
try {
await DoWorkAsync(); // 每次都是新 ValueTask
break;
} catch { /* ... */ }
}.AsTask(),接受分配开销 —— var vt = GetOperation();
var t = vt.AsTask();
await t;
await t; // OK
有些写法看似只 await 了一次,实则在编译或运行时触发了多次 —— 特别要注意 async 方法体内的 await 表达式求值顺序和捕获上下文的副作用。
var tasks = list.Select(x => DoAsync(x));
await Task.WhenAll(tasks); // 这里每个 DoAsync(x) 返回新 ValueTask,没问题
// ❌ 但如果写成 list.Select(_ => vt).ToArray(),就真在复用同一个 vt
ValueTask:每次调用 getter 应返回新实例;若缓存了 ValueTask 字段并反复返回它,就会踩坑await vt:VS 调试器会真实执行 await,导致后续代码中的 await 失败重复 await 一个 ValueTask 不是边界情况,而是明确禁止的操作。它的“一次性”是契约级约束,不是优化副作用。只要变量生命周期跨过一次
