示例代码
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace Test
{
internal class Program
{
static void Main(string[] args)
{
_mainThreadSynchronizationContext = new ThreadSynchronizationContext("main");
_threadSynchronizationContext = new ThreadSynchronizationContext("thread");
if (SynchronizationContext.Current == null)
{
Console.WriteLine("Main SynchronizationContext.Current is null");
}
SynchronizationContext.SetSynchronizationContext(_mainThreadSynchronizationContext);
if (SynchronizationContext.Current != null)
{
Console.WriteLine("Main SynchronizationContext.Current is not null!");
}
Thread thread = new Thread(ThreadLoop);
thread.Start();
MainLoop();
}
private static ThreadSynchronizationContext _mainThreadSynchronizationContext;
private static ThreadSynchronizationContext _threadSynchronizationContext;
private static void MainLoop()
{
MainTest();
while (true)
{
Thread.Sleep(1);
_mainThreadSynchronizationContext.Update();
}
}
private static void ThreadLoop()
{
if (SynchronizationContext.Current == null)
{
Console.WriteLine("Thread SynchronizationContext.Current is null");
}
SynchronizationContext.SetSynchronizationContext(_threadSynchronizationContext);
if (SynchronizationContext.Current != null)
{
Console.WriteLine("Thread SynchronizationContext.Current is not null");
}
Thread.Sleep(10000);
ThreadTest();
while (true)
{
Thread.Sleep(1);
_threadSynchronizationContext.Update();
}
}
private static async Task MainTest()
{
Console.WriteLine($"MainTest {Thread.CurrentThread.ManagedThreadId}");
await TaskNew();
Console.WriteLine($"MainTest End {Thread.CurrentThread.ManagedThreadId}");
}
private static async Task ThreadTest()
{
Console.WriteLine($"ThreadTest {Thread.CurrentThread.ManagedThreadId}");
await TaskNew();
Console.WriteLine($"ThreadTest End {Thread.CurrentThread.ManagedThreadId}");
}
private static Task TaskNew()
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine($"TaskNew Begin {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(5000);
Console.WriteLine($"TaskNew End {Thread.CurrentThread.ManagedThreadId}");
});
}
public class ThreadSynchronizationContext : SynchronizationContext
{
// 线程同步队列,发送接收socket回调都放到该队列,由poll线程统一执行
private readonly ConcurrentQueue<Action> queue = new();
private Action a;
private string name;
public ThreadSynchronizationContext(string name)
{
this.name = name;
}
public void Update()
{
while (true)
{
if (!this.queue.TryDequeue(out a))
{
return;
}
try
{
a();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
public override void Post(SendOrPostCallback callback, object state)
{
this.Post(() => callback(state));
}
public void Post(Action action)
{
Console.WriteLine($"Post: {name}");
this.queue.Enqueue(action);
}
}
}
}
输出结果
Thread SynchronizationContext.Current is null
Thread SynchronizationContext.Current is not null
MainTest 1
TaskNew Begin 4
TaskNew End 4
Post: main
MainTest End 1
ThreadTest 3
TaskNew Begin 5
TaskNew End 5
Post: thread
ThreadTest End 3
SynchronizationContext
这个类的作用是将代码从一个线程转移到另外一个线程执行。
Send 表示同步执行代码快,会阻塞当前线程,直到Send执行完成。
Post 表示异步执行代码快,不阻塞当前线程。
SynchronizationContext.Current 与 SynchronizationContext.SetSynchronizationContext
这个静态属性与方法,是线程隔离的,也就是线程1 调用SynchronizationContext.SetSynchronizationContext。线程2 通过 SynchronizationContext.Current 是获取不到值的,从上面例子中也可以看出。
可以看到 SynchronizationContext 的源码,Send 方法是当前线程同步调用。Post 是从线程池中找一个线程进行调用。
SynchronizationContext.Current 这个值在不同环境中的主线程结果是不一样的。在控制台情况下默认是NULL,在WindowForm的情况下是 WindowsFormsSynchronizationContext。在Unity中是 UnitySynchronizationContext。
当通过await Task开启一个异步任务,当任务完成后会调用SynchronizationContext.Current.Post 方法。在例子中可以看出。如果SynchronizationContext.Current 是空,则会使用默认值SynchronizationContext对象。通过这个特性,重写SynchronizationContext 就可以让线程执行完成Task后,回调到原来线程进行执行,这个经常需要使用到。