c# 委托的BeginInvoke和EndInvoke是什么 异步委托和Task的区别

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

BeginInvoke/EndInvoke 是 .NET 早期 APM(异步编程模型)下委托的异步调用机制,本质是线程池 + 回调;而 Task 是 TPL(任务并行库)提供的现代异步抽象,更灵活、可组合、支持 await —— 它们不是同一层概念,也不推荐混用。

为什么现在几乎不用
BeginInvoke
/
EndInvoke

它们属于已过时(obsolete-in-practice)的异步模式,仅在极少数遗留 WinForms 控件跨线程调用(如

Control.BeginInvoke
)中还有意义,但那和委托的
BeginInvoke
不是一回事。委托自身的
BeginInvoke
在 .NET Core / .NET 5+ 中已被标记为“不推荐使用”,且无对应
await
支持。

必须成对调用
EndInvoke
,否则会泄漏线程池资源(哪怕你不关心返回值)
无法用
await
,强行包装成
Task
需要手动桥接,性能差、代码丑
没有取消支持(
CancellationToken
),错误处理靠异常捕获 +
IAsyncResult
状态轮询
回调函数(
AsyncCallback
)在线程池线程执行,容易引发 UI 线程访问异常(比如直接更新 WPF 控件)

Task
怎么替代委托的异步调用?

直接把耗时逻辑封装进

Func<tresult></tresult>
Func<t tresult></t>
,再用
Task.Run
托管到线程池 —— 简单、可控、可
await
、可取消。

public delegate int HeavyCalcDelegate(int x, int y);
// ❌ 过时写法(不推荐)
HeavyCalcDelegate calc = (a, b) => { Thread.Sleep(1000); return a + b; };
IAsyncResult ar = calc.BeginInvoke(5, 3, null, null);
int result = calc.EndInvoke(ar); // 阻塞等待
// ✅ 现代写法(推荐)
Task<int> task = Task.Run(() => { Thread.Sleep(1000); return 5 + 3; });
int result2 = await task; // 或 task.Result(阻塞)
Task.Run
明确表达“我要在后台线程跑这个”,语义清晰
支持
await
ContinueWith
WhenAll
等组合操作
天然集成
CancellationToken
Task.Run(..., token)
异常自动封装进
AggregateException
,统一处理

什么时候真得用
BeginInvoke

基本没有。唯一常见例外是 WinForms 中跨线程更新 UI:

// 注意:这是 Control 的 BeginInvoke,不是 Delegate 的!
this.BeginInvoke(new Action(() =>
{
    label1.Text = "Done";
}));
这个
BeginInvoke
Control
类的方法,内部用 Windows 消息机制实现线程切换
它和委托类的
BeginInvoke
同名但完全无关,签名、行为、原理都不同
WPF 中应改用
Dispatcher.InvokeAsync
await Dispatcher.InvokeAsync

真正该警惕的是:看到

BeginInvoke
就条件反射想“是不是要异步”,先确认它是哪个类型的成员 —— 委托?控件?还是自定义类?多数情况下,你想要的只是
Task.Run
async/await

相关推荐