Task 默认调度器不同导致 await 行为差异
.NET Framework 的
Task默认使用
WindowsFormsSynchronizationContext(WinForms)或
AspNetSynchronizationContext(ASP.NET Web Forms / MVC),
await后会自动回到原始上下文线程;.NET Core 完全移除了这些上下文,
await默认继续在线程池线程执行,不保证返回原上下文。 ASP.NET Core 中
ConfigureAwait(false)已无实际意义——因为本来就没有
SynchronizationContext.NET Framework 下 WPF/WinForms 项目若未显式调用
ConfigureAwait(false),UI 线程 await 后续代码可能被阻塞,引发死锁(尤其在同步等待
Task.Result时) ASP.NET Framework(非 Core)中,同步阻塞异步调用(如
task.Wait())极易触发请求上下文死锁,而 ASP.NET Core 不会
ThreadPool 默认配置与可伸缩性表现不同
.NET Core 从 2.1 起采用自适应线程池(
ThreadPool.UnmanagedThreadPool启用优化),初始最小线程数默认为 1(
ThreadPool.SetMinThreads不再影响底层行为);.NET Framework 的线程池依赖固定启发式算法,最小线程数默认为逻辑处理器数,且长期存在“饥饿”问题——高并发短任务下线程增长滞后,导致延迟突增。 .NET Core 中
ThreadPool.GetMinThreads返回值可能与实际调度不符,不应依赖它做容量预估 .NET Framework 下手动调用
ThreadPool.SetMinThreads(100, 100)是常见调优手段;.NET Core 中该调用仅影响托管线程池的“软限制”,底层 unmanaged pool 仍自主伸缩 在突发 I/O 密集型负载(如大量
HttpClient请求)下,.NET Core 线程池响应更快,排队时间更短
AsyncLocal 跨 await 的数据传递行为一致但需注意作用域泄漏
两者都通过
ExecutionContext流传
AsyncLocal<t></t>值,语义一致。但 .NET Framework 在某些旧版 IIS 集成模式(如 Classic Mode)中,
ExecutionContext可能意外截断;.NET Core 则始终保证完整传播。 在中间件或过滤器中设置
AsyncLocal<string> _traceId = new()</string>,后续所有
await调用都能读取,无需额外捕获 务必避免将
AsyncLocal<t></t>实例存储到静态字段并跨请求复用——值会在请求间“残留”,造成数据污染 .NET Framework 下若禁用
executionContext(配置
<legacycorruptedstateexceptionspolicy enabled="true"></legacycorruptedstateexceptionspolicy>),
AsyncLocal会失效;.NET Core 不支持该配置,始终启用
并行集合与 PLINQ 的线程安全模型没有本质变化,但默认并发度策略更激进
ConcurrentDictionary<tkey tvalue></tkey>、
ConcurrentQueue<t></t>等类型 API 完全兼容,行为一致。区别在于 PLINQ(
AsParallel())默认最大并行度:.NET Framework 使用
Environment.ProcessorCount,而 .NET Core 2.0+ 默认设为
ProcessorCount * 2(I/O 或混合负载更友好)。 PLINQ 查询中若含非线程安全操作(如写入普通
List<t></t>),无论哪个平台都会出错——不能依赖“默认更安全” 在 CPU 密集型场景下,.NET Core 的更高默认并行度可能导致上下文切换开销上升,此时应显式调用
WithDegreeOfParallelism(4)
Parallel.ForEach的
MaxDegreeOfParallelism行为一致,但 .NET Core 对
ParallelOptions.TaskScheduler的自定义调度器支持更稳定
var options = new ParallelOptions
{
MaxDegreeOfParallelism = 4,
TaskScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler
};
Parallel.ForEach(items, options, item => { /* ... */ });.NET Core 的并发模型更贴近现代硬件与云环境,但这也意味着:你不能再靠“碰巧没出问题”的旧习惯(比如在 ASP.NET Framework 里同步阻塞异步方法)蒙混过关——错误会立刻暴露,而不是等压测时才崩。
