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。
