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 应用里,这个“允许”的情况越来越少了。
