GitHub原地址:什么是协程
黑体字为作者(熊猫大佬)原创,红色为个人理解
什么是协程
说到协程,我们先了解什么是异步,异步简单说来就是,我要发起一个调用,但是这个被调用方(可能是其它线程,也可能是IO)出结果需要一段时间,我不想让这个调用阻塞住调用方的整个线程,因此传给被调用方一个回调函数,被调用方运行完成后回调这个回调函数就能通知调用方继续往下执行。举个例子:
下面的代码,主线程一直循环,每循环一次sleep 1毫秒,计数加一,每10000次打印一次。
private static void Main()
{
int loopCount = 0;
while (true)
{
int temp = watcherValue;
Thread.Sleep(1);
++loopCount;
if (loopCount % 10000 == 0)
{
Console.WriteLine($"loop count: {loopCount}");
}
}
}
这时我需要加个功能,在程序一开始,我希望在5秒钟之后打印出loopCount的值。看到5秒后我们可以想到Sleep方法,它会阻塞线程一定时间然后继续执行。我们显然不能在主线程中Sleep,因为会破坏掉每10000次计数打印一次的逻辑。
// example2_1
class Program
{
private static int loopCount = 0;
private static void Main()
{
OneThreadSynchronizationContext _ = OneThreadSynchronizationContext.Instance;
WaitTimeAsync(5000, WaitTimeFinishCallback);
while (true)
{
OneThreadSynchronizationContext.Instance.Update();
Thread.Sleep(1);
++loopCount;
if (loopCount % 10000 == 0)
{
Console.WriteLine($"loop count: {loopCount}");
}
}
}
private static void WaitTimeAsync(int waitTime, Action action)
{
Thread thread = new Thread(()=>WaitTime(waitTime, action));
thread.Start();
}
private static void WaitTimeFinishCallback()
{
Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}");
}
/// <summary>
/// 在另外的线程等待
/// </summary>
private static void WaitTime(int waitTime, Action action)
{
Thread.Sleep(waitTime);
// 将action扔回主线程执行
OneThreadSynchronizationContext.Instance.Post((o)=>action(), null);
}
}
我们在这里设计了一个WaitTimeAsync方法,WaitTimeAsync其实就是一个典型的异步方法,它从主线程发起调用,传入了一个WaitTimeFinishCallback回调方法做参数,开启了一个线程,线程Sleep一定时间后,将传过来的回调扔回到主线程执行。OneThreadSynchronizationContext是一个跨线程队列,任何线程可以往里面扔委托,OneThreadSynchronizationContext的Update方法在主线程中调用,会将这些委托取出来放到主线程执行。为什么回调方法需要扔回到主线程执行呢?因为回调方法中读取了loopCount,loopCount在主线程中也有读写,所以要么加锁,要么永远保证只在主线程中读写。加锁是个不好的做法,代码中到处是锁会导致阅读跟维护困难,很容易产生多线程bug。这中将逻辑打包成委托然后扔回另外一个线程多线程开发中常用的技巧。
我们可能又有个新需求,WaitTimeFinishCallback执行完成之后,再想等3秒,再打印一下loopCount。
private static void WaitTimeAsync(int waitTime, Action action)
{
Thread thread = new Thread(()=>WaitTime(waitTime, action));
thread.Start();
}
private static void WaitTimeFinishCallback()
{
Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}");
WaitTimeAsync(3000, WaitTimeFinishCallback2);
}
private static void WaitTimeFinishCallback2()
{
Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}");
}
我们这时还可能改需求,需要在程序启动5秒后,接下来4秒,再接下来3秒,打印loopCount,也就是上面的逻辑中间再插入一个3秒等待。
private static void WaitTimeAsync(int waitTime, Action action)
{
Thread thread = new Thread(()=>WaitTime(waitTime, action));
thread.Start();
}
private static void WaitTimeFinishCallback()
{
Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}");
WaitTimeAsync(4000, WaitTimeFinishCallback3);
}
private static void WaitTimeFinishCallback3()
{
Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}");
WaitTimeAsync(3000, WaitTimeFinishCallback2);
}
private static void WaitTimeFinishCallback2()
{
Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}");
}
这样中间插入一段代码,显得非常麻烦。这里可以回答什么是协程了,其实这一串串回调就是协程。
运行结果
以下是我个人理解与笔记
先来看OneThreadSynchronizationContext类,他继承了SynchronizationContext类,而SyncehronizationContext是干嘛的呢?
SynchronizationContext提供在各种同步模型中传播同步上下文的基本功能。
而对于OneThreadSynchronizationContext,我的理解就是,收集各个线程的回调方法,并且放到主线程进行。具体为何熊猫在上面已说明。
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace ETModel
{
public class OneThreadSynchronizationContext : SynchronizationContext
{
/// <summary>
/// 单例
/// </summary>
public static OneThreadSynchronizationContext Instance { get; } = new OneThreadSynchronizationContext();
/// <summary>
/// 线程ID
/// </summary>
private readonly int mainThreadId = Thread.CurrentThread.ManagedThreadId;
// 线程同步队列,发送接收socket回调都放到该队列,由poll线程统一执行
private readonly ConcurrentQueue<Action> queue = new ConcurrentQueue<Action>();
/// <summary>
/// 一个不含参的委托
/// </summary>
private Action a;
public void Update()
{
while (true)
{
//尝试将队列的第一个委托出队,如果成功出队,则执行委托,否则结束这一次判断
if (!this.queue.TryDequeue(out a))
{
return;
}
a();
}
}
/// <summary>
/// 将回调方法(委托)入队
/// </summary>
/// <param name="callback">回调方法</param>
/// <param name="state">这个我不清楚有什么用,所以不做注释</param>
public override void Post(SendOrPostCallback callback, object state)
{
if (Thread.CurrentThread.ManagedThreadId == this.mainThreadId)
{
callback(state);
return;
}
this.queue.Enqueue(() => { callback(state); });
}
}
}
我们注意到,在代码的最后一行,有一个()=>符号,这是兰布达表达式,它实质上是声明了一个不含参的委托,后面的callback(state)是它内部的的具体方法。
那么Post的具体作用就是,如果当前入队的线程是主线程,就直接执行其回调方法,否则就将其入队,在Update轮询。
我们回到主程序
在第一步执行WaitTimeAsync,并将WaitTimeFinishCallback注册为回调方法,在第二步里面,我们又看到了()=>符号,在这里是什么意思呢
我们先来看Thread
所以我们知道,WaitTime被封装成了委托,他将在Thread.Start();的时候执行,至此,本篇笔记到此结束,如果还不懂,请大家自己好好琢磨。