在实现微线程时第一个要解决的问题就是统一处理异步、同步阻塞和同步非阻塞操作。异步和同步阻塞操作都需要借助外部线程处理并通过回调函数获得执行结果。当然了对于同步的情况你可以把交给线程/线程池执行的方法看做回调函数。而对于同步非阻塞操作因为它不必借助外部线程而直接运行在微线程的宿主线程中,所以我们需要借助协程(yield)来“阻塞”它一下以便其他的微线程有运行的机会。因此我定义了一个接口来统一处理这个问题:
public interface INonBlock
{
//是否同步非阻塞
bool IsSync { get; }
//唯一编号
Guid Id { get; }
//异步执行
void AsyncInvoke();
//同步执行
IEnumerable<INonBlock> SyncInvoke();
}
然后我再定义一个抽象类对该接口进一步包装:
public abstract class NonBlock : INonBlock
{
Guid _id;
//同步执行时需要的空操作,用于让出时间片
Nop _nop;
protected virtual bool IsSync
{
get { return false; }
}
//异步、同步阻塞执行后的结果
public object NonBlockResult { get; private set; }
public NonBlock()
{
_id = Guid.NewGuid();
_nop = new Nop();
}
//同步,异步执行后利用此函数通知宿主线程任务完成
protected void Finish(object result)
{
NonBlockResult = result;
Tasklets.Instance.ResumeTask(this);
} //对Nop的包装
protected INonBlock Schedule()
{
return _nop;
}
protected abstract void AsyncInvoke();
protected abstract IEnumerable<INonBlock> SyncInvoke();
#region INonBlock
//省略...
#endregion
}
以下是Nop的实现:
//Nop不会执行任何操作
class Nop : INonBlock
{
Guid _id = Guid.NewGuid();
public Nop() { }
Guid INonBlock.Id { get { return _id; } }
bool INonBlock.IsSync
{
get { return true; }
}
#region INonBlock
//省略...
#endregion
}
INonBlock是整个微线程运行的核心,所有的协作都是基于对这个接口的yield和迭代完成的。而微线程只是对INonBlock做一些组织工作和包装所以其接口非常简单:
public interface ITasklet
{
IEnumerable<INonBlock> Run();
}
最后就是Tasklets了它负责执行和调度微线程,在它的内部有一个真正执行任务的宿主线程Thread _coroutine.它做的事情也很简单:
private void CoroutineStart()
{
IEnumerator<INonBlock> iter = null;
ITasklet task = null;
//等待任务
while (_event.WaitOne())
{
lock (_tasks)
{
task = _tasks.Dequeue();
iter = task.Run().GetEnumerator();
}
//如果yield出了INonBlock就将该任务挂起
if (iter.MoveNext())
SuspendTask(task, iter);
}
}
在挂起任务(SuspendTask)的时候执行INonBlock中的异步操作或同步操作。
private void SuspendTask(ITasklet tasklet, IEnumerator<INonBlock> iter)
{
lock (_suspension)
{
//将INonBlock再次包装为可执行的任务
if (!(tasklet is IterTask))
tasklet = new IterTask(iter);
var iterTask = tasklet as IterTask;
//获取INonBlock的Id用于唤醒操作
iterTask.NonBlockId = iter.Current.Id;
var nonblock = iter.Current;
//如果是Nop直接放入任务队列等待执行
if (nonblock is Nop)
Add(iterTask);
else
{
//将同步执行的代码放入任务队列,Add函数可将其包装为ITasklet
if(nonblock.IsSync)
Add(nonblock.SyncInvoke);
else
nonblock.AsyncInvoke();
//挂起,对于同步执行来说,其父INonBlock被挂起
_suspension.Add(iterTask);
}
}
}
在这里需要准备的是,对于调用了同步执行的Tasklet。它会产生两个INonBlock,一个是从ITasklet.Run接口中产生的,一个是其INonBlock。SyncInvoke产生的。这个地方可能会让你有点乱。最后的唤醒任务就很简单了:
public void ResumeTask(INonBlock nonblock)
{
lock (_suspension)
{
foreach (IterTask item in _suspension)
{
if (item.NonBlockId == nonblock.Id)
{
_suspension.Remove(item);
Add(item);
break;
}
}
}
}
到此为止整个微线程的运行流程就结束了,本质上就是一堆INonBlock在不停的迭代。零散的代码片段可能如容易让你理解,请下载Demo跟踪一遍流程就会完全明了。