本文参考了包建强兄的“WF本质论 读书心得” (http://www.cnblogs.com/jax/archive/2008/02/21/1076632.html),他给出了部分的源代码, 我修改了一点,再添加上他没有给出的部分,就基本是一个可用的OpenSesame的源代码了。
首先肯定,WF本质论是本不可多得的好书。作者提出了许多令人耳目一新的见解,重点是用bookmark(也就是continuation的概念)来把本来会耗时很长并无端占用线程的操作解耦,通过delegate转换成可以在不同线程进程中执行的两部分,也就是作者所谓的thread agility & process agility.
第一章应该是全书的关键,理解了第一章就为理解WF的内部机制打下了坚实的基础。而作者用了一个贯穿了整个第一章例子,OpenSesame,芝麻开门来阐述他的观点,建议所有的读者都应该亲自动手来实现一下这个OpenSesame的例子,绝对可以加深理解。可惜的是,作者没有给出这个例子的完整代码,这里缺一块,那里少个角,掖着藏着让人很不舒服。Google了一下也没有全部的源码,所以我觉得花点时间把这个芝麻开门的代码写出来很有必要。需要声明的是,这个代码绝对不是什么最终的或是官方的版本,只是到目前为止我对本书第一章的理解而已。只是供大家参考,希望可以给正在看“WF本质论”的朋友有所帮助。
个人感觉关键的地方有这几点:
1)thread agility。 刚开始被书里的BeginReadLine例子误导,总想着用BeginInvoke/EndInvoke去神奇的实现长耗时操作并不占用线程(比如实现Console.ReadLine),后来才发现作者不是这个意思。作者把一个长耗时操作分成了两部分,上半部分(在代码中就是ProgramStatement.Run方法)只是创建一个bookmark,就是设定一个delegate,并没有其他的操作,也不需要异步,只是设定,然后上半部分就结束。下半部分(代码中的ContinueAt)就是本操作在获得了外界的输入后应该做的动作。奥妙就在于本来需要由这个长耗时操作占用一个线程进行的漫长而又无用的等待的工作(比如Console.ReadLine,需要等待用户输入),现在可以由主线程提供通知,从而使长耗时操作的下半部分得以进行下去。这样的好处就是,本来在等待输入的过程中的是要有个线程的,而现在就没了。
简言之,大家可以这样想,原先的情况是有一个主线程A,然后主线程A又生成了一个专门给长耗时操作用的线程B,在线程B里面需要Console.ReadLine,B得到输入后会进行一些操作,这样的话任何时候都会有两个线程。如果用了bookmark的方法,主线程A产生线程B调用bookmark的上半部分,然后线程B结束,这时我们在主线程中进行Console.ReadLine,得到了输入后我们再开一个线程B2,继续bookmark下半部分的调用,这样我们就节约了在等待用户输入时的一个线程。这就是作者所谓的thread agility。其实主线程是很忙的,实际运用中不可能停下来等待用户输入,它能做的应该是在它得到了用户输入/外界消息后(比如通过message queue),通知并调用bookmark的下半部分。
2)书中的每个ProgramStatement(也就是WF的Activity)是通过BookmarkManager.Done()来启动的。
3) Passivate是实现process agility的手段,在代码中没有实现。
4)Binding. 细心的读者可能发现在书中作者没有给出怎么把上个ProgramStatement的输出赋值给下个ProgramStatement的代码,作者狡猾的用省略号表示了。因为这个绑定的动作发生在ProgramStatement实际的执行以前,如果我们没有做特殊处理的话,在执行的时候我们会得到null reference exception。在代码中就是PrintGreeting的Run方法内。在运行本程序的时候你会发现无论你输入什么,你总是得到"OKOK",因为我偷懒,没有用特殊方法去处理。我想到的一个不破坏整个bookmark逻辑的处理方法就是用.net AOP,在PrintGreeting的key和s属性上加上特殊的attribute,这样当这两个属性需要被用到的时候会去另外的ProgramStatement(Read,PrintKey)取。
5)主线程内,当有了Console.ReadLine的输入后,通过BookmarkManager.Resume去继续Bookmark的下半部的执行。相当于WF中,用WorkflowInstance.EnqueueItem()去继续流程。
6)ProgramHandle类,我觉得这个类有点鸡肋,没什么存在的必要。
附上OpenSesame的代码,欢迎大家共同讨论。
Code
using System;
using System.Collections.Generic;
using System.Threading;
namespace OpenSesame
{
public delegate void BookmarkLocation(Bookmark resumed);
[Serializable]
public class Bookmark
{
public Bookmark(string name, BookmarkLocation continueAt)
{
Name = name;
ContinueAt = continueAt;
}
public string Name { get; set; }
public BookmarkLocation ContinueAt { get; set; }
public object Payload { get; set; }
public BookmarkManager BookmarkManager { get; set; }
}
public class BookmarkManager
{
private List<Bookmark> bookmarkList;
private ProgramStatement currentProgramStatement;
public BookmarkManager()
{
bookmarkList = new List<Bookmark>();
}
public void Add(Bookmark bookmark)
{
bookmarkList.Add(bookmark);
bookmark.BookmarkManager = this;
}
public void Remove(Bookmark bookmark)
{
bookmarkList.Remove(bookmark);
}
public void Resume(string bookmarkName, object payload)
{
foreach (Bookmark bookmark in bookmarkList)
{
if (bookmark.Name == bookmarkName)
{
bookmark.Payload = payload;
bookmark.ContinueAt(bookmark);
break;
}
}
}
// Request execution of a program statement, using an
// implicit bookmark that will be resumed when that
// program statement completes its execution
public void RunProgramStatement(ProgramStatement statement, BookmarkLocation continueAt)
{
currentProgramStatement = statement;
Bookmark bookmark = new Bookmark(statement.GetType().FullName, continueAt);
Add(bookmark);
statement.Run(this);
}
// Indicate that the current program statement is done,
// so that internally managed bookmarks can be resumed
public void Done(bool bAllDone)
{
if (!bAllDone)
Resume(currentProgramStatement.GetType().FullName, currentProgramStatement);
else
bookmarkList.Clear();
}
}
[Serializable]
public abstract class ProgramStatement
{
public abstract void Run(BookmarkManager mgr);
}
public class MythicalRuntime
{
Dictionary<ProgramHandle, ProgramStatement> ht;
private BookmarkManager mgr = new BookmarkManager();
public MythicalRuntime()
{
ht = new Dictionary<ProgramHandle, ProgramStatement>();
}
public BookmarkManager Mgr
{
get { return mgr; }
set { mgr = value; }
}
public ProgramHandle RunProgram(ProgramStatement program)
{
//这个新的Guid根据规则创建,而不是简单的new Guid(),以下仅为模拟方法
Guid programId = new Guid();
ProgramHandle programHandle = new ProgramHandle();
programHandle.ProgramId = programId;
ht.Add(programHandle, program);
//Bookmark的上半部分,用新的thread执行
ThreadPool.QueueUserWorkItem(state => program.Run(state as BookmarkManager), Mgr);
return programHandle;
}
public ProgramHandle GetProgramHandle(Guid programId)
{
//根据programId恢复已经钝化的程序,假设恢复为read方法
ProgramStatement program = new Read();
//重新构建ProgramHandle
ProgramHandle programHandle = new ProgramHandle();
programHandle.ProgramId = programId;
//重新加载到内存
ht.Add(programHandle, program);
return programHandle;
}
public void Shutdown()
{
//从内存中取出所有ProgramHandle, 依次钝化
foreach (ProgramHandle tmpProgramHandle in ht.Keys)
{
ProgramStatement program = ht[tmpProgramHandle];
tmpProgramHandle.Passivate(program);
}
ht = null;
}
}
public class ProgramHandle
{
private Guid programId;
public Guid ProgramId
{
get { return programId; }
set { programId = value; }
}
public void Passivate(ProgramStatement program)
{
//将program根据关键字programId进行钝化
}
public void Resume(string bookmarkName, object payload)
{
BookmarkManager mgr = new BookmarkManager();
mgr.Resume(bookmarkName, payload);
}
}
[Serializable]
public class Read : ProgramStatement
{
private string text;
public string Text
{
get { return text; }
}
public override void Run(BookmarkManager mgr)
{
mgr.Add(new Bookmark("read", ContinueAt));
}
void ContinueAt(Bookmark resumed)
{
text = (string)resumed.Payload;
BookmarkManager mgr = resumed.BookmarkManager;
mgr.Remove(resumed);
mgr.Done(false);
}
}
[Serializable]
public class PrintKey : ProgramStatement
{
private string key;
public string Key
{
get { return key; }
}
public override void Run(BookmarkManager mgr)
{
// Print the key
key = DateTime.Now.Millisecond.ToString();
Console.WriteLine("here is your key: " + key);
mgr.Done(false);
}
}
[Serializable]
public class PrintGreeting : ProgramStatement
{
private string key;
public string Key
{
get { return key; }
set { key = value; }
}
private string s;
public string Input
{
get { return s; }
set { s = value; }
}
public override void Run(BookmarkManager mgr)
{
//没有做特殊处理,key肯定是null.
if (string.IsNullOrEmpty(key))
{
Console.WriteLine("OKOK");
return;
}
// Print the greeting if the key is provided
if (key.Equals(s))
Console.WriteLine("hello, world");
else
{
Console.WriteLine("Wrong key!");
}
mgr.Done(false);
}
}
[Serializable]
public class ProgramStatementBlock : ProgramStatement
{
int currentIndex;
List<ProgramStatement> statements = new List<ProgramStatement>();
public IList<ProgramStatement> Statements
{
get { return statements; }
}
public override void Run(BookmarkManager mgr)
{
currentIndex = 0;
// Empty statement block
if (statements.Count == 0)
mgr.Done(true);
else
mgr.RunProgramStatement(statements[0], ContinueAt);
}
public void ContinueAt(Bookmark resumed)
{
BookmarkManager mgr = resumed.BookmarkManager;
// If we've run all the statements, we're done
if (++currentIndex == statements.Count)
mgr.Done(true);
else // Else, run the next statement
mgr.RunProgramStatement(statements[currentIndex], ContinueAt);
}
}
public class OpenSesame_v3
{
static void Main(string[] args)
{
ProgramStatementBlock openSesameProgram = new ProgramStatementBlock();
PrintKey printKey = new PrintKey();
Read read = new Read();
PrintGreeting printGreeting = new PrintGreeting();
printGreeting.Key = printKey.Key;
printGreeting.Input = read.Text;
openSesameProgram.Statements.Add(printKey);
openSesameProgram.Statements.Add(read);
openSesameProgram.Statements.Add(printGreeting);
MythicalRuntime runtime = new MythicalRuntime();
ProgramHandle handle = runtime.RunProgram(openSesameProgram);
string s = Console.ReadLine();
//Bookmark的下半部分,用新的thread执行
ThreadPool.QueueUserWorkItem(state => runtime.Mgr.Resume("read", state), s);
// keep the main thread running
Console.ReadLine();
}
}
}