c# SynchronizationContext.Post 和 Send 的区别

来源:这里教程网 时间:2026-02-21 17:41:45 作者:

Post 是异步投递,Send 是同步调用

Post
把委托排队到目标上下文(比如 UI 线程),立即返回,不等执行;
Send
会阻塞当前线程,直到委托在目标上下文执行完才返回。这决定了它们的适用场景完全不同:需要避免卡住调用方时用
Post
,必须确保某段逻辑在目标上下文完成后再继续时才考虑
Send

Post
在 WinForms 中最终走
Control.BeginInvoke
,WPF 中走
Dispatcher.BeginInvoke
Send
在 WinForms 中对应
Control.Invoke
,WPF 中是
Dispatcher.Invoke
,但注意:.NET Core / .NET 5+ 的默认
SynchronizationContext
(如
ThreadPoolSynchronizationContext
)根本不支持
Send
,调用会直接抛
NotSupportedException
在 ASP.NET Core 或控制台应用中,若没显式安装自定义上下文(如
AsyncLocalSynchronizationContext
),
Current
通常为
null
,此时调用
Post
Send
都会触发
NullReferenceException

Send 可能引发死锁,Post 一般不会

典型死锁场景:UI 线程调用

await Task.Run(() => { context.Send(...); })
,而
Send
又试图在 UI 线程执行——此时 UI 线程正被
await
卡住,无法处理
Send
请求,形成循环等待。而
Post
不阻塞,只发消息就返回,不会卡住调用线程,因此天然规避这类问题。

WinForms/WPF 中,从 UI 线程调用
Send
到自身上下文是安全的(即同一线程同步调用),但跨线程调用
Send
风险极高
即使在支持
Send
的上下文中,也应优先用
Post
+
TaskCompletionSource
模拟同步语义,而不是直接用
Send
某些自定义
SynchronizationContext
(如旧版 ASP.NET 的
AspNetSynchronizationContext
)重写了
Send
,使其退化为
Post
+ 自旋等待,这种“伪同步”仍可能耗尽线程池资源

Post 的委托执行时机不可控,Send 的执行时机相对确定

Post
提交的任务会被放入目标上下文的消息队列尾部,需等前面所有待处理消息(包括输入事件、计时器回调等)执行完才轮到它;
Send
虽然也排队,但在多数 UI 上下文中会尝试“插队”或尽快调度(例如 WPF 的
Dispatcher.Invoke
默认用
Normal
优先级,但可指定
Send
级别)。

WinForms 中
Post
等价于
BeginInvoke
,任务进入 Windows 消息循环的
QS_POSTMESSAGE
队列,受系统消息调度影响
WPF 中
Post
对应
Dispatcher.BeginInvoke
,优先级固定为
Background
,低于输入/渲染等关键任务
如果需要比默认
Post
更快响应,WPF 可改用
Dispatcher.InvokeAsync
并传入
DispatcherPriority.Loaded
或更高,但这已脱离
SynchronizationContext
接口范畴
var context = SynchronizationContext.Current;
if (context == null)
{
    // 当前无上下文,不能 Post/Send
    Console.WriteLine("No SynchronizationContext available");
}
else
{
    context.Post(_ => Console.WriteLine("This runs asynchronously on target context"), null);
    // 下一行立即执行,不等上面那句输出
    try
    {
        context.Send(_ => Console.WriteLine("This blocks until done"), null);
        // 这行执行完,上面那句才一定已输出
    }
    catch (NotSupportedException ex)
    {
        // .NET Core 默认上下文不支持 Send
        Console.WriteLine($"Send not supported: {ex.Message}");
    }
}
真正容易被忽略的是:很多开发者以为只要
SynchronizationContext.Current != null
就能安全调用
Send
,实际上还得看具体实现是否允许——而现代 .NET 应用里,这个“允许”的情况越来越少了。

相关推荐