.NET Core中将异步库委托回调封送到其定义线程执行的问题咨询
问题描述
需求
假设存在一个处理异步事件的库(例如UDP广播),要求向该库传入delegate时,保证该delegate在其被定义的线程中执行。
核心实现代码
public void Run(Action<string> action) { var PORT = 10000; var udpClient = new UdpClient(); // Start listener udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, PORT)); var endpoint = new IPEndPoint(0, 0); //var scheduler = TaskScheduler.Current; // 无异常但delegate在其他线程执行 var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); // 抛出异常 Task.Factory.StartNew(() => { while (true) { action(Encoding.UTF8.GetString(udpClient.Receive(ref endpoint))); } }, CancellationToken.None, TaskCreationOptions.None, scheduler); // Start sender var data = Encoding.UTF8.GetBytes("PING"); udpClient.Send(data, data.Length, "255.255.255.255", PORT); var aTimer = new System.Timers.Timer(); aTimer.Elapsed += new ElapsedEventHandler(OnTimer); aTimer.Interval = 1000; aTimer.Enabled = true; void OnTimer(object source, ElapsedEventArgs e) { var data = Encoding.UTF8.GetBytes("PONG"); udpClient.Send(data, data.Length, "255.255.255.255", PORT); } }
线程检测代码
Console.WriteLine("Start on Thread : " + Thread.CurrentThread.ManagedThreadId); new Beacon().Run(response => { Console.WriteLine("Continue on Thread : " + Thread.CurrentThread.ManagedThreadId); });
异常现象
- 使用
TaskScheduler.Current时无异常,但delegate仍在其他线程执行 - 使用
TaskScheduler.FromCurrentSynchronizationContext()时抛出异常:The current SynchronizationContext may not be used as a TaskScheduler
待解答问题
- 为什么在.NET Core中无法使用synchronization context,该如何正确使用该组件?
- 是否存在更优的方案将delegate调用封送到主线程执行?
- BackgroundWorker和EventLoopScheduler目前是否已经过时?
解答
问题1解答
.NET Core本身支持SynchronizationContext,你遇到报错是因为默认控制台应用没有内置的同步上下文实现:只有WPF、WinForms、旧版ASP.NET等带消息循环的框架才会在启动时初始化对应SynchronizationContext,控制台程序启动时SynchronizationContext.Current为null,调用FromCurrentSynchronizationContext自然会抛出异常。
正确使用方式:
- 如果是UI框架场景,直接在UI线程调用该方法即可拿到对应调度器,回调会自动封送到UI线程执行
- 如果是控制台等无内置同步上下文的场景,需要自行实现单线程同步上下文,或使用第三方调度组件绑定到指定线程
问题2解答
更优方案按场景区分:
- UI应用场景:直接使用对应框架的原生封送方法即可,比如WPF用
Dispatcher.Invoke、WinForms用Control.Invoke,不需要额外实现 - 控制台/通用场景:
- 用
Channel<T>实现生产者消费者模型:UDP后台线程收到数据后写入Channel,主线程循环读取Channel执行delegate,天然保证回调在主线程执行 - 引入Reactive Extensions,使用
EventLoopScheduler绑定到指定线程,收到数据后把delegate调度到该Scheduler执行 - 自行实现绑定固定线程的
TaskScheduler,收到数据后将delegate作为任务提交到该Scheduler执行
另外你现有代码的逻辑存在错误:把整个UDP监听死循环提交到目标调度器执行,会直接占死目标线程,根本无法处理其他任务。正确逻辑是监听逻辑跑在后台线程,仅在收到数据后将delegate的调用提交到目标调度器执行。
- 用
问题3解答
BackgroundWorker已经过时,.NET生态现在统一使用Task+async/await替代,后者对取消、异常处理、并行编排的支持远优于BackgroundWorker,无特殊需求不建议再使用EventLoopScheduler是Reactive Extensions的核心组件,没有过时,在需要自定义线程调度、事件流编排的复杂场景下仍然是首选方案,只是简单场景不需要额外引入Rx依赖,用内置的Task、Channel即可满足需求
内容的提问来源于stack exchange,提问作者Anonymous
相关产品推荐
相关产品推荐

