在.net4.0以上版本,提供了async、await关键词,可以很好的解决异步操作代码顺序问题,可以像写同步代码一样顺序编写代码,非常容易阅读。但.net4.0中的异步还是要依赖回调,代码嵌套层次过多,看代码的时候容易晕。
比如软件中有一个工作线程,接收工作任务,任务执行完毕后,通知调用方,调用方继续下发任务,这些任务顺序执行,组合起来完成一个功能。
//工作线程对象
private Thread _workThread = null;
//工作任务队列
private List<tuple<string, action>> _taskList = new List<tuple<string, action>>();
//启动工作线程
private void button3_Click(object sender, EventArgs e)
{
_workThread = new Thread(new ThreadStart(() => {
while (true)
{
if (_taskList.Count > 0)
{
var task = _taskList[0];
_taskList.RemoveAt(0);
task.Item4(string.Format("{0}_{1}_{2}", task.Item1, task.Item2, task.Item3));
Thread.Sleep(5000);
}
else
{
Thread.Sleep(20);
}
}
}));
_workThread.IsBackground = true;
_workThread.Start();
}
//传统方式 很多嵌套回调
private void button4_Click(object sender, EventArgs e)
{
//注册第一个异步
_taskList.Add(new Tuple<string, action>(DateTime.Now.ToString(), 1, 1, new Action((str1) => {
Debug.WriteLine(str1);
//第一个异步执行完毕,在注册第二个异步
_taskList.Add(new Tuple<string, action>(DateTime.Now.ToString(), 1, 1, new Action((str2) => { Debug.WriteLine(str2);
//...继续注册下去,很多嵌套层
})));
})));
}
//改进方式:
IEnumerator _call = null;
private IEnumerable Execute()
{
string value = string.Empty;
var callback = new Action((str) => {
value = str;
Debug.WriteLine(DateTime.Now.ToString() + " callback run");
_call.MoveNext();
});
//触发工作线程工作,并退出执行,等待下一次调用MoveNext
//即给工作线程一个动作,工作线程执行完动作后,调用_call.MoveNext()函数,这样主线程的foreach语句就行执行下一次循环了。
_taskList.Add(new Tuple<string, action>(DateTime.Now.ToString(), 0, 0, callback));
yield return 0;
_taskList.Add(new Tuple<string, action>(DateTime.Now.ToString(), 1, 1, callback));
yield return 1;
//工作线程根据回调函数通知请求执行完毕
Debug.WriteLine(DateTime.Now.ToString() + " over");
yield return 2;
}
private void button5_Click(object sender, EventArgs e)
{
_call = Execute().GetEnumerator();
_call.MoveNext();
}
改进方式还是要依赖工作线程执行完毕的回调通知,但在写业务逻辑的时候,避免了大量回调嵌套,代码可读性得到改善。
由于具体任务是在子线程中执行的,UI界面保持正常刷新。
yield如果不跨线程执行,感觉也没有什么作用,只是一个语法糖,提供了创建IEnumerable对象的方案而已,变通一下还是可以改进代码质量的。