环境:
- window10
- vs2019
- .net core 3.1
- dnSpy v6.1.4 (.NET Core)
- ILSpy版本6.1.0.5902
一、为什么需要SynchronizationContext?参考:《C# SynchronizationContext线程上下文简单说明》
顺便说下: 单独看SynchronizationContext意义不大,因为它是一个基类,在真正使用它的时候一般都要继承并覆盖其方法的(如:winform)。
问题是这样的:在winform中窗体的控件都是由UI线程创建的,当我们点击按钮后如果有耗时操作的话,我们肯定需要单独启动线程去运算,运算完了之后又要访问窗体控件将结果显示出来,但因为窗体控件是由UI线程创建的,所以,winform仅允许我们在UI线程中去操纵窗体控件。
所以就有了下面的常见代码:
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "运算中...";
button1.Enabled = false;
Task.Run(() =>
{
//耗时操作
Thread.Sleep(3000);
//运算完成
button1.BeginInvoke((MethodInvoker)delegate
{
button1.Text = "点击运算";
button1.Enabled = true;
});
});
}
现在尝试换一种写法,使用SynchronizationContext可以达到相同的效果:
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "运算中...";
button1.Enabled = false;
var ctx = SynchronizationContext.Current;
Task.Run(() =>
{
//耗时操作
Thread.Sleep(3000);
//运算完成
ctx.Send(state =>
{
button1.Text = "点击运算";
button1.Enabled = true;
}, null);
});
}
从上面看,SynchronizationContext好像是将代码变得简洁了一些。当时微软推出SynchronizationContext的时候是想做一个多线程协同工作的标准(参考:《并行计算 SynchronizationContext 综述》)。
二、SynchronizationContext的特点- 首先,这是一个基类,它只提供了基本的实现,如果要使用它大多都需要继承并覆盖其中的方法;
- 其次,它提供了一个在多个线程间同步工作的标准(比如:线程A需要线程B协助做一些事情等等)。
为了更好的理解SynchronizationContext,我们在控制台实现一个多线程同步模型,目的是让两个线程在执行的时候相互协作。整个过程模拟的是Winform事件处理代码,即上面的示例。
- 第一步,因为SynchronizationContext本身只做了简单的定义,不满足我们的需要,所以定义一个MySynchronizationContext(上面示例代码中的SynchronizationContext.Current也不是SynchronizationContext类型,而是WindowsFormsSynchronizationContext):
-
public class MySynchronizationContext : SynchronizationContext { private readonly MyControl ctrl; public MySynchronizationContext(MyControl ctrl) { this.ctrl = ctrl; } //让主线程(UI线程)同步执行d方法 public override void Send(SendOrPostCallback d, object state) { ctrl.Invoke(state => d(state), state); } //让主线程(UI线程)异步执行d方法 public override void Post(SendOrPostCallback d, object state) { ctrl.BeginInvoke(state => d(state), state); } } - 第二步,为了模拟窗体控件,我们自定义一个MyControl类,它在主线程(UI线程)中被创建,对它的修改也要在主线程中进行,代码如下:
public class MyControl { //记录创建这个控件的线程(UI线程) private Thread _createThread = null; public MyControl() { //第一个控件创建时初始化一个SynchronizationContext实例,并将它和当前线程绑定一起 if (SynchronizationContext.Current == null) SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext(this)); _createThread = Thread.CurrentThread; //初始化一个字典队列,key: 线程Id,value:参数队列 ExeArg.QueueDics.TryAdd(Thread.CurrentThread.ManagedThreadId, new BlockingCollection()); } //同步调用 public void Invoke(Action - 第三步,为了封装委托和参数,我们需要定义一个模型:
public class ExeArg { //要放到主线程(UI线程)中执行的方法 public ActionAction { get; set; } //方法执行时额外的参数 public object State { get; set; } //是否同步执行 public bool Sync { get; set; } //静态字典,key: 线程Id,value: 队列 public static ConcurrentDictionary > QueueDics = new ConcurrentDictionary >(); //当前线程对应的字典 public static BlockingCollection CurrentQueue => QueueDics.ContainsKey(Thread.CurrentThread.ManagedThreadId) ? QueueDics[Thread.CurrentThread.ManagedThreadId] : null; } - 第四步,现在我们开始模拟整个流程
public static void Main(string[] arg) { Console.WriteLine($"主线程: {Thread.CurrentThread.ManagedThreadId}"); //主线程创建控件 var ctrl = new MyControl(); var syncContext = SynchronizationContext.Current; //模拟一个用户操作 Task.Run(() => { //模拟耗时操作 Thread.Sleep(2000); Console.WriteLine($"用户线程: {Thread.CurrentThread.ManagedThreadId},Post前"); syncContext.Post((state) => { //可以在这里修改MyControl的值等 Console.WriteLine($"Post内的方法执行线程: {Thread.CurrentThread.ManagedThreadId},参数:{state}"); }, new { name = "小明" }); Console.WriteLine($"用户线程: {Thread.CurrentThread.ManagedThreadId},Post后,Send前"); syncContext.Send((state) => { //可以在这里修改MyControl的值等 //为了体现Send的同步效果,这里睡3秒 Thread.Sleep(3000); Console.WriteLine($"Send内的方法执行线程: {Thread.CurrentThread.ManagedThreadId},参数:{state}"); }, new { name = "小红" }); Console.WriteLine($"用户线程: {Thread.CurrentThread.ManagedThreadId},Send后"); }); //主线程开启消息垒 while (true) { var exeArg = ExeArg.CurrentQueue.Take(); exeArg.Action?.Invoke(exeArg.State); } }
将程序运行起来,我们观察下结果:
从运行的结果上来看,我们达到了类Winform的多线程协作的目的。
四、Winform中怎样使用SynchronizationContext的?其实,上面写的控制台示例程序就是本人在看过winform的相关代码后写出来的,它们逻辑大体相同。
- 首先,Winform中用的是WindowsFormsSynchronizationContext,它继承自SynchronizationContext并重写了Send和Post方法,在重写的时候调用了控件的Invoke和BeginInvoke方法,如下:
- 然后,SynchronizationContext.Current的值是在第一个Control控件创建的时候被初始化的,如下图:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlTypes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace Test
{
class Program
{
public static void Main(string[] arg)
{
Console.WriteLine($"主线程: {Thread.CurrentThread.ManagedThreadId}");
//主线程创建控件
var ctrl = new MyControl();
var syncContext = SynchronizationContext.Current;
//模拟一个用户操作
Task.Run(() =>
{
Thread.Sleep(2000);
Console.WriteLine($"用户线程: {Thread.CurrentThread.ManagedThreadId},Post前");
syncContext.Post((state) =>
{
Console.WriteLine($"Post内的方法执行线程: {Thread.CurrentThread.ManagedThreadId},参数:{state}");
}, new { name = "小明" });
Console.WriteLine($"用户线程: {Thread.CurrentThread.ManagedThreadId},Post后,Send前");
syncContext.Send((state) =>
{
Thread.Sleep(3000);
Console.WriteLine($"Send内的方法执行线程: {Thread.CurrentThread.ManagedThreadId},参数:{state}");
}, new { name = "小红" });
Console.WriteLine($"用户线程: {Thread.CurrentThread.ManagedThreadId},Send后");
});
//主线程开启消息垒
while (true)
{
var exeArg = ExeArg.CurrentQueue.Take();
exeArg.Action?.Invoke(exeArg.State);
}
}
}
public class ExeArg
{
//要放到主线程(UI线程)中执行的方法
public Action Action { get; set; }
//方法执行时额外的参数
public object State { get; set; }
//是否同步执行
public bool Sync { get; set; }
//静态字典,key: 线程Id,value: 队列
public static ConcurrentDictionary> QueueDics = new ConcurrentDictionary>();
//当前线程对应的字典
public static BlockingCollection CurrentQueue => QueueDics.ContainsKey(Thread.CurrentThread.ManagedThreadId) ? QueueDics[Thread.CurrentThread.ManagedThreadId] : null;
}
public class MySynchronizationContext : SynchronizationContext
{
private readonly MyControl ctrl;
public MySynchronizationContext(MyControl ctrl)
{
this.ctrl = ctrl;
}
public override void Send(SendOrPostCallback d, object state)
{
ctrl.Invoke(state => d(state), state);
}
public override void Post(SendOrPostCallback d, object state)
{
ctrl.BeginInvoke(state => d(state), state);
}
}
public class MyControl
{
//记录创建这个控件的线程(UI线程)
private Thread _createThread = null;
public MyControl()
{
//第一个控件创建时初始化一个SynchronizationContext实例,并将它和当前线程绑定一起
if (SynchronizationContext.Current == null) SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext(this));
_createThread = Thread.CurrentThread;
//初始化一个字典队列,key: 线程Id,value:参数队列
ExeArg.QueueDics.TryAdd(Thread.CurrentThread.ManagedThreadId, new BlockingCollection());
}
//同步调用
public void Invoke(Action action, object state)
{
var queues = ExeArg.QueueDics[_createThread.ManagedThreadId];
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
queues.Add(new ExeArg()
{
Action = obj =>
{
action(state);
manualResetEvent.Set();
},
State = state,
Sync = true
});
manualResetEvent.WaitOne();
manualResetEvent.Dispose();
}
//异步调用
public void BeginInvoke(Action action, object state)
{
var queues = ExeArg.QueueDics[_createThread.ManagedThreadId];
queues.Add(new ExeArg()
{
Action = action,
State = state
});
}
}
}



