BeginInvoke/EndInvoke 是已过时的委托异步调用机制,因资源泄漏、无取消支持、不可 await 等缺陷被 Task.Run + async/await 取代;仅 WinForms 的 Control.BeginInvoke 因消息机制例外存在。

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 或 Func,再用 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 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。










