环境:
参考:《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
,我们在控制台实现一个多线程同步模型,目的是让两个线程在执行的时候相互协作。整个过程模拟的是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<ExeArg>());
}
//同步调用
public void Invoke(Action<object> 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<object> action, object state)
{
var queues = ExeArg.QueueDics[_createThread.ManagedThreadId];
queues.Add(new ExeArg()
{
Action = action,
State = state
});
}
}
public class ExeArg
{
//要放到主线程(UI线程)中执行的方法
public Action<object> Action { get; set; }
//方法执行时额外的参数
public object State { get; set; }
//是否同步执行
public bool Sync { get; set; }
//静态字典,key: 线程Id,value: 队列
public static ConcurrentDictionary<int, BlockingCollection<ExeArg>> QueueDics = new ConcurrentDictionary<int, BlockingCollection<ExeArg>>();
//当前线程对应的字典
public static BlockingCollection<ExeArg> 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的多线程协作的目的。
SynchronizationContext
的?其实,上面写的控制台示例程序就是本人在看过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<object> Action { get; set; }
//方法执行时额外的参数
public object State { get; set; }
//是否同步执行
public bool Sync { get; set; }
//静态字典,key: 线程Id,value: 队列
public static ConcurrentDictionary<int, BlockingCollection<ExeArg>> QueueDics = new ConcurrentDictionary<int, BlockingCollection<ExeArg>>();
//当前线程对应的字典
public static BlockingCollection<ExeArg> 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<ExeArg>());
}
//同步调用
public void Invoke(Action<object> 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<object> action, object state)
{
var queues = ExeArg.QueueDics[_createThread.ManagedThreadId];
queues.Add(new ExeArg()
{
Action = action,
State = state
});
}
}
}