利用SynchronizationContext.Current在线程间同步上下文

简而言之就是允许一个线程和另外一个线程进行通讯,SynchronizationContext在通讯中充当传输者的角色。另外这里有个地方需要清楚的,不是每个线程都附加SynchronizationContext这个对象,只有UI线程是一直拥有的。

在多线程操作时往往需要切回某个线程中去工作,等完成后再切回来。如主UI线程中创建了一个子线程A。A中添加了委托事件。UI线程中向A线程的类注册了事件,当A线程触发事件时去修改UI上的属性如TEXT。这个时候往往要在UI线程向子线程注册的事件方法中使用控件的invoke方法才能访问UI线程中的控件,因为这些注册的事件(委托)方法代码虽然看似写在UI线程的Form类中,但实际上是注册在了子线程A的事件中,它们是会被子线程A触发事件时在子线程内部执行的。这样,我们不得不在主UI线程的类的注册事件方法中通过控件的invoke方法才能访问控件,这样做十分麻烦。我们想和系统的控件事件一样,直接在注册的事件方法中访问控件。那么我们想,有没有一个机制能让子线程在执行UI线程委托过来的(注册的事件)方法中直接访问控件本身呢?答案是肯定的。对过SynchronizationContext.Current 。

SynchronizationContext.Current 能得到当前被主UI线程接管过的对象SynchronizationContext。

这个对象有一个方法:SynchronizationContext.Send(SendOrPostCallback d,object state)

d 为一个没有返回值,并且具有一个Object类型传入参数的委托(SendOrPostCallback )

state 为执行这个委托时的参数(object)


你现在需要在子线程运行的时候利用SynchronizationContext.Current 得到SynchronizationContext对象,然后在需要切回UI线程的地方使用:

SynchronizationContext.Send(SendOrPostCallback d,object state);就可实现将委托的方法切回到UI线程上去执行。


注意:SynchronizationContext的对象不是所有线程都被附加的,只有UI主线程会被附加。对于UI线程来说,是如何将SynchronizationContext这个对象附加到线程上的呢?

在Form1 form = new Form1()之前,SynchronizationContext对象是为空,而当实例化Form1窗体后,SynchronizationContext对象就被附加到这个线程上了。所以可以得出答案了:当Control对象被创建的同时,SynchronizationContext对象也会被创建并附加到线程上。所有在使用时,一定要等窗体InitializeComponent(); 这个完成后 它才能得到一个不这NULL的对象

可以参考这里:http://lzy3169421.blog.163.com/blog/static/1135452772009357251417/


最后Synchronizatio

<think>我们正在讨论如何在ThreadPool.QueueUserWorkItem中使用SynchronizationContext来更新UI。 根据引用[1]和引用[3],我们知道: - 在UI线程中,SynchronizationContext.Current会返回一个特定的同步上下文(如WindowsFormsSynchronizationContext),它重写了Post和Send方法,通过消息泵机制将委托调度到UI线程执行。 - 在后台线程中,我们可以获取UI线程SynchronizationContext(在UI线程中获取并保存),然后在后台线程中通过调用Post或Send方法来更新UI。 具体步骤: 1.UI线程中(例如按钮点击事件处理程序中)获取当前的SynchronizationContext(它代表UI线程同步上下文)。 2. 将获取的SynchronizationContext实例传递给后台线程(通过ThreadPool.QueueUserWorkItem的参数传递)。 3. 在后台线程中,使用该SynchronizationContext的Post方法(异步)或Send方法(同步)来执行更新UI的操作。 注意:Post是异步的,不会阻塞后台线程,而Send是同步的,会等待UI操作完成。在更新UI时,通常推荐使用Post以避免阻塞后台线程。 根据引用[3]中的代码示例,我们可以这样写: 在UI线程中: private void button_Click(object sender, EventArgs e) { // 获取当前(UI线程)的同步上下文 var context = SynchronizationContext.Current; // 启动后台任务,并将context传递给它 ThreadPool.QueueUserWorkItem(BackgroudRun, context); } 后台方法: private void BackgroudRun(object state) { var context = state as SynchronizationContext; // 使用Post方法异步更新UI context.Post(new SendOrPostCallback(o => { // 在这里更新UI控件,例如: label1.Text = "Hello from ThreadPool!"; }), null); } 注意:在Windows Forms中,我们也可以使用Control.Invoke或Control.BeginInvoke,但使用SynchronizationContext更通用(也适用于WPF、ASP.NET等,尽管在ASP.NET Core中默认没有同步上下文)。 另外,引用[1]提到,在UI线程中,SynchronizationContext的Post方法实际上是通过消息泵(message pump)来将委托发送到UI线程的消息队列中,然后由UI线程处理。 因此,要实现在ThreadPool.QueueUserWorkItem中更新UI,关键是将UI线程SynchronizationContext捕获并传递给后台线程,然后使用它的Post方法。 下面给出一个完整的示例: 假设我们有一个Windows Forms应用程序,其中有一个按钮和一个标签(label1)。 按钮点击事件处理程序:</think>### 在 ThreadPool.QueueUserWorkItem 中使用 SynchronizationContext 更新 UI.NET 中,通过 `ThreadPool.QueueUserWorkItem` 执行后台任务时,需要安全地更新 UI 线程。以下是完整实现步骤(基于引用[1][3][4]): #### 核心实现步骤 1. **在 UI 线程捕获同步上下文** 在 UI 事件(如按钮点击)中获取当前同步上下文: ```csharp private void button_Click(object sender, EventArgs e) { // 获取当前 UI 线程同步上下文 SynchronizationContext uiContext = SynchronizationContext.Current; // 启动后台任务并传递同步上下文 ThreadPool.QueueUserWorkItem(BackgroundTask, uiContext); } ``` 2. **后台任务中通过 Post 更新 UI** 使用捕获的上下文异步更新 UI: ```csharp private void BackgroundTask(object state) { // 1. 后台处理逻辑 Thread.Sleep(2000); // 模拟耗时操作 string result = "操作完成"; // 2. 安全更新 UI var uiContext = (SynchronizationContext)state; uiContext.Post(_ => { // 此代码在 UI 线程执行 label1.Text = result; progressBar.Value = 100; }, null); } ``` #### 关键机制说明 1. **SynchronizationContext 作用** - UI 框架(如 WinForms/WPF)会重写 `Post()` 方法(引用[1]) - 内部通过**消息泵机制**将委托调度到 UI 线程的消息队列 - 避免了直接跨线程访问 UI 的异常 2. **为什么用 Post 而不是 Send** - `Post()`:异步非阻塞(推荐) ```csharp // 引用[1]中的默认实现 public virtual void Post(SendOrPostCallback d, Object state) { ThreadPool.QueueUserWorkItem(new WaitCallback(d), state); } ``` - `Send()`:同步阻塞(可能导致死锁) 3. **ThreadPool 注意事项** - 线程线程默认为 MTA 单元(引用[2][4]) - 通过 `SynchronizationContext.Post` 可安全桥接到 UI 线程的 STA 单元 #### 完整示例代码 ```csharp // WinForms 示例 public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnStart_Click(object sender, EventArgs e) { // 捕获 UI 线程上下文 var uiContext = SynchronizationContext.Current; // 启动后台任务 ThreadPool.QueueUserWorkItem(state => { // 后台处理 for (int i = 0; i <= 100; i++) { Thread.Sleep(50); // 通过 Post 更新进度 uiContext.Post(progress => { progressBar.Value = (int)progress; }, i); } // 完成通知 uiContext.Post(_ => { labelStatus.Text = "处理完成!"; btnStart.Enabled = true; }, null); }); btnStart.Enabled = false; } } ``` #### 注意事项 1. **上下文验证** 在 `Post` 前检查上下文是否有效: ```csharp if (uiContext != null && !uiContext.Equals(SynchronizationContext.Current)) { uiContext.Post(...); } ``` 2. **异常处理** 在委托内部添加 try-catch: ```csharp uiContext.Post(_ => { try { /* UI 操作 */ } catch (Exception ex) { /* 处理异常 */ } }, null); ``` 3. **跨平台差异** - .NET Framework:WinForms/WPF 有内置同步上下文 - .NET Core:需显式设置 `WindowsFormsSynchronizationContext.AutoInstall = true` > 此模式适用于 WinForms/WPF/UWP 等 UI 框架,通过消息泵机制确保线程安全[^1][^3][^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值