c# 关于同步线程上下文

前言

什么是同步线程上下文呢?

这里面有一些需求,就是有些代码需要在同一个线程运行。

避免一些并发问题啥的。

正文

例子:

自定义上下文:

public class SingleThreadSynchronizationContext : SynchronizationContext, IDisposable
{
    private readonly BlockingCollection<(SendOrPostCallback Callback, object State)> _queue = new();
    private readonly Thread _processingThread;

    public SingleThreadSynchronizationContext()
    {
        _processingThread = new Thread(ProcessQueue)
        {
            IsBackground = true,
            Name = "SingleThreadSynchronizationContext Thread"
        };
        _processingThread.Start();
    }

    // 异步方式派发工作
    public override void Post(SendOrPostCallback d, object state)
    {
        _queue.Add((d, state));
    }

    // 同步方式派发工作(可能阻塞)
    public override void Send(SendOrPostCallback d, object state)
    {
        if (Thread.CurrentThread == _processingThread)
        {
            d(state); // 如果已经在目标线程,直接执行
        }
        else
        {
            using var signal = new ManualResetEventSlim();
            Post(_ =>
            {
                d(_);
                signal.Set();
            }, state);
            signal.Wait();
        }
    }

    private void ProcessQueue()
    {
        // 设置当前线程的上下文
        SetSynchronizationContext(this);
        Console.WriteLine(SynchronizationContext.Current == null);
        Console.WriteLine($"ProcessQueue: {Thread.CurrentThread.ManagedThreadId}");

        foreach (var work in _queue.GetConsumingEnumerable())
        {
            work.Callback(work.State);
        }
    }

    public void Dispose()
    {
        _queue.CompleteAdding();
        _processingThread.Join();
        _queue.Dispose();
    }
}

然后去取:

static async Task Main()
{
	Console.WriteLine($"Main Thread: {Thread.CurrentThread.ManagedThreadId}");

	// 创建并设置自定义上下文
	using var context = new SingleThreadSynchronizationContext();
	SynchronizationContext.SetSynchronizationContext(context);

	// 演示异步操作如何回到特定线程
	await Task.Run(() =>
	{
		Console.WriteLine(SynchronizationContext.Current == null);
		Console.WriteLine($"Task.Run Thread: {Thread.CurrentThread.ManagedThreadId}");
	}).ConfigureAwait(true);

	Console.WriteLine($"After await Thread: {Thread.CurrentThread.ManagedThreadId}");

	// 演示Send/Post区别
	context.Post(_ => 
	{
		Console.WriteLine($"Post Thread: {Thread.CurrentThread.ManagedThreadId}");
	}, null);

	context.Send(_ => 
	{
		Console.WriteLine($"Send Thread: {Thread.CurrentThread.ManagedThreadId}");
	}, null);

	Console.WriteLine("All operations completed");
	
	Console.ReadKey();
}

这样就可以同步上下文了,也就是在指定的上下文中执行了。

taskschedule 方式:

// 自定义单线程任务调度器
public class SingleThreadTaskScheduler : TaskScheduler, IDisposable
{
    private readonly BlockingCollection<Task> _tasks = new();
    private readonly Thread _thread;

    public SingleThreadTaskScheduler(string threadName = null)
    {
        _thread = new Thread(() =>
        {
            foreach (var task in _tasks.GetConsumingEnumerable())
            {
                TryExecuteTask(task);
            }
        })
        {
            IsBackground = true,
            Name = threadName ?? "SingleThreadTaskScheduler"
        };
        _thread.Start();
    }

    protected override IEnumerable<Task> GetScheduledTasks() => _tasks.ToArray();
    protected override void QueueTask(Task task) => _tasks.Add(task);
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => 
        Thread.CurrentThread == _thread && TryExecuteTask(task);

    public void Dispose()
    {
        _tasks.CompleteAdding();
        _thread.Join();
    }
}

// 使用示例
async Task RunWithTaskScheduler()
{
    using var scheduler = new SingleThreadTaskScheduler("CustomTaskSchedulerThread");
    
    await Task.Factory.StartNew(async () =>
    {
        Console.WriteLine($"Current Thread: {Thread.CurrentThread.Name}");
        await Task.Delay(100);
        Console.WriteLine($"Still on thread: {Thread.CurrentThread.Name}");
    }, CancellationToken.None, TaskCreationOptions.None, scheduler).Unwrap();
}

这里面是原理,然后呢?实际上,有一个东西是taskschedule 默认给我们提供了一个同步的上下文的调度器。

internal sealed class MyForm : Form {
    private readonly TaskScheduler m_syncContextTaskScheduler;
    public MyForm() {
        // 获得对一个同步上下文任务调度器的引用
        m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

        Text = "Synchronization Context Task Scheduler Demo";
        Visible = true; Width = 400; Height = 100;
    }


    private CancellationTokenSource m_cts;

    protected override void OnMouseClick(MouseEventArgs e) {
        if (m_cts != null) {    // 一个操作正在进行,取消它
            m_cts.Cancel();
            m_cts = null;
        } else {    // 操作没有开始,启动它
            // 操作没有开始,启动它
            Text = "Operation running";
            m_cts = new CancellationTokenSource();

            // 这个任务使用默认任务调度器,在一个线程池线程上执行
            Task<Int32> t = Task.Run(() => Sum(m_cts.Token, 20000), m_cts.Token);

            // 这些任务使用同步上下文任务调度器,在 GUI 线程上执行
            t.ContinueWith(task => Text = "Result: " + task.Result,
                CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, m_syncContextTaskScheduler);

            t.ContinueWith(task => Text = "Operation canceled",
                CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, m_syncContextTaskScheduler);

            t.ContinueWith(task => Text = "Operation faulted",
                CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, m_syncContextTaskScheduler);
        }
        base.OnMouseClick(e);
    }
}

是的就是这个:TaskScheduler.FromCurrentSynchronizationContext(), 差不多和上面写的一样,可以自行去阅读源码

同步上下文大概就是这么回事,不是去线程池同步上下文,线程池最好是没有上下文的,这样才符合上下文的特征。

posted @ 2025-04-14 18:43  敖毛毛  阅读(47)  评论(0)    收藏  举报