我的Workflow之旅--自己写简单workflow(一)
写这个文章是08年为了给以前公司的一次工作流知识的培训,现在拿出来给大家分享分享。有很多不足之处,请见谅。
•首先我们来看看Workflow历史:
工作流技术发端于 1970 年代中期办公自动化领域的研究工作,但工作流思想的出现还应该更早, 1968 年 Fritz Nordsieck 就已经清楚地表达了利用信息技术实现工作流程自动化的想法。 1970 年代与工作流有关的研究工作包括:宾夕法尼亚大学沃顿学院的 Michael D. Zisman 开发的原型系统 SCOOP ,施乐帕洛阿尔托研究中心的 Clarence A. Ellis 和 Gary J. Nutt 等人开发的 OfficeTalk 系列试验系统,还有 Anatol Holt 和 Paul Cashman 开发的 ARPANET 上的“监控软件故障报告”程序。 SCOOP, Officetalk 和 Anatol Holt 开发的系统都采用 Petri 网的某种变体进行流程建模。其中 SCOOP 和 Officetalk 系统,不但标志着工作流技术的开始,而且也是最早的办公自动化系统。
1970 年代人们对工作流技术充满着强烈乐观情绪,研究者普遍相信新技术可以带来办公效率的巨大改善,然而这种期望最终还是落空了。人们观察到这样一种现象,一个成功的组织往往会在适当的时候创造性的打破标准的办公流程;而工作流技术的引入使得人们只能死板的遵守固定的流程,最终导致办公效率低和人们对技术的反感。 1970 年代工作流技术失败的技术原因则包括:在办公室使用个人计算机尚未被社会接受,网络技术还不普遍,开发者还不了解群件技术的需求与缺陷。
含有工作流特征的商用系统的开发始于 1983 年至 1985 年间,早期的商用系统主要来自于图像处理领域和电子邮件领域。图像处理许多时候需要流转和跟踪图像,工作流恰好迎合这种需求;增强的电子邮件系统也采用了工作流的思想,把原来点对点的邮件流转改进为依照某种流程来流转。在这些早期的工作流系统中只有少数获得了成功。
进入 1990 年代以后,相关的技术条件逐渐成熟,工作流系统的开发与研究进入了一个新的热潮。据调查,截至 1995 年共有 200 多种软件声称支持工作流管理或者拥有工作流特征。工作流技术被应用于电讯业、软件工程、制造业、金融业、银行业、科学试验、卫生保健领域、航运业和办公自动化领域。
•在你做系统时候你否会考虑到以下问题呢?
1)所谓交互式程序就是程序在执行期间依赖外部实体的刺激,并对此做出响应。而这些交互式程序都将花费大量的时间在等待这些刺激。
2)怎么简单而清晰的保证控制流正确性。
对于这两个问题大家也许首先会想到用线程来解决这些:
•线程的灵活性
用下面的代码来模拟用户交互
class Program
{
static void Main(string[] args)
{
Console.WriteLine("you key is:ok");
if(Console.ReadLine()=="ok")
Console.WriteLine("hello ,will come");
Thread.Sleep(new TimeSpan(0, 0, 5));
}
}
这段代码是程序主程序一直处于了等待状态,这样干不了别的活咯。现在就得请线程这位大哥来帮忙,让等待脱离主线程,怎么做呢?
public class AsyncDemo
{
public AsyncDemo()
{
m_delegate = new runDelegate(Run);
}
private delegate string runDelegate();
private runDelegate m_delegate;
public System.IAsyncResult BeginReadLine(System.AsyncCallback asynaCallback, object state)
{
Console.WriteLine("BeginReadLine当为前线程为: " + Thread.CurrentThread.GetHashCode());
return m_delegate.BeginInvoke(asynaCallback, state);
}
public string EndReadLine(System.IAsyncResult ar)
{
if (ar == null)
throw new NullReferenceException("参数未空");
Console.WriteLine("EndReadLine当为前线程为: " + Thread.CurrentThread.GetHashCode());
return m_delegate.EndInvoke(ar);
}
public string Run()
{
Console.WriteLine("Run当为前线程为: " + Thread.CurrentThread.GetHashCode());
return Console.ReadLine();
}
}
class Program
{
static void Main(string[] args)
{
AsyncDemo demo = new AsyncDemo();
string key = new Random().Next(100).ToString();
Console.WriteLine("you key is:" + key);
IAsyncResult result = demo.BeginReadLine(null, null);
//主程序继续干它的活。。
for(int i=0; i<10;i++)
{
//睡觉。。。。
Thread.Sleep(new TimeSpan(0, 0, 1));
}
//主程序睡觉的某个时候 我们输入了key 马上就拿到了
string s = demo.EndReadLine(result);
if (key.Equals(s))
Console.WriteLine("hello, will come!");
Thread.Sleep(new TimeSpan(0,0,5));
}
}
用异步委托来开启新的线程来等待交互的输入值。ok,搞定,我们没有让主程序干它不想干的(等待)。它做了喜欢做的睡觉,睡醒后它就拿到了交互输入的key值。
但是很快我们发现一大堆代码都需要在主程序里关联,交互多就越来越复杂啦。我也不知道那个在什么时候就完成交互,怎么办呢? 脑袋一闪,把key能全局访问,再给他回调函数,不就ok了么,赶紧看看:
public static class AsyncDemo
{
static AsyncDemo()
{
m_delegate = new runDelegate(Run);
}
private delegate string runDelegate();
private static runDelegate m_delegate;
public static System.IAsyncResult BeginReadLine(System.AsyncCallback asynaCallback, object state)
{
Console.WriteLine("BeginReadLine当为前线程为: " + Thread.CurrentThread.GetHashCode());
return m_delegate.BeginInvoke(asynaCallback, state);
}
public static string EndReadLine(System.IAsyncResult ar)
{
if (ar == null)
throw new NullReferenceException("参数未空");
Console.WriteLine("EndReadLine当为前线程为: " + Thread.CurrentThread.GetHashCode());
return m_delegate.EndInvoke(ar);
}
public static string Run()
{
Console.WriteLine("Run当为前线程为: " + Thread.CurrentThread.GetHashCode());
return Console.ReadLine();
}
}
class Program
{
static string key;
static void Main(string[] args)
{
key = new Random().Next(100).ToString();
Console.WriteLine("here you key:" + key);
Console.WriteLine("Main当为前线程为: " + Thread.CurrentThread.GetHashCode());
//用户交互结束后直接 按原计划ContinueAt处理
AsyncDemo.BeginReadLine(ContinueAt, null);
//我继续干我的活。。
Thread.Sleep(Timeout.Infinite);
}
static void ContinueAt(IAsyncResult ar)
{
string s = AsyncDemo.EndReadLine(ar);
Console.WriteLine("ContinueAt当为前线程为: " + Thread.CurrentThread.GetHashCode());
if (key.Equals(s))
Console.WriteLine("hello, will come!");
Thread.Sleep(new TimeSpan(0, 0, 5));
Environment.Exit(0);
}
}
我们通过异步调用将程序逻辑拆分成几块,又连接在一起。 比较:原来程序中key是一个基于栈的局部变量,现在,key是一个静态字段,它具有跨方法的可见性。对于强类型的数据,这种栈无关的方法是降低程序对给定线程的依赖性的关键。
启发:ContinueAt方法的委托运行起来活似一个书签(Bookmark)------一个逻辑定位点,当受到适当的外部刺激后,程序可以从这个位置恢复执行。
•我们就来尝试做——书签
怎么做呢?
1):我们可以给书签命名,还可以为书签提供一个管理器。
2):这个书签因该可以序列化,可以从持久存储介质存取书签。
3):我们可以编写一个监听器程序,需要分发到书签去的数据必须由这个程序来分发。
不管那么多了,先上代码:
public delegate void BookmarkLocation(Bookmark resumed);
[Serializable]
public class Bookmark
{
private string _Name;
private object _Payload;
private BookmarkManager _BookmarkManager;
private BookmarkLocation _CountinueAt;
public Bookmark(string name,BookmarkLocation countinueAt)
{
this._Name = name;
this._CountinueAt = countinueAt;
}
public string Name
{
get { return _Name; }
}
public BookmarkLocation CountinueAt
{
get { return _CountinueAt; }
}
public object Payload
{
get { return _Payload; }
set { _Payload = value; }
}
public BookmarkManager BookmarkManager
{
get { return _BookmarkManager; }
set { _BookmarkManager = value; }
}
}
public static class BookmarkStorage
{
static BookmarkStorage()
{
_marks=new List<Bookmark>();
}
private static List<Bookmark> _marks;
public static List<Bookmark> Bookmarks
{
get { return _marks; }
}
}
public class BookmarkManager
{
public BookmarkManager()
{
}
public void Add(Bookmark bookmark)
{
if (bookmark != null)
{
bookmark.BookmarkManager = this;
BookmarkStorage.Bookmarks.Add(bookmark);
}
}
public void Remove(Bookmark bookmark)
{
BookmarkStorage.Bookmarks.Remove(bookmark);
}
public void Resume(string bookmarkNmae, Object payload)
{
Bookmark mark = BookmarkStorage.Bookmarks.Where(a => a.Name == bookmarkNmae).FirstOrDefault();
if(mark!=null)
{
mark.Payload=payload;
//mark.CountinueAt.Invoke(mark);//直接调用
Console.WriteLine(DateTime.Now.ToString() +"Resume当为前线程为: " + Thread.CurrentThread.GetHashCode());
IAsyncResult result = mark.CountinueAt.BeginInvoke(mark, null, payload);
//result.AsyncWaitHandle.WaitOne();//等待线程同步
Console.WriteLine(DateTime.Now.ToString() +"Resume当为前线程为: " + Thread.CurrentThread.GetHashCode());
//mark.CountinueAt.EndInvoke(result);
}
}
}
[Serializable]
public class OpenSesame
{
string key;
public void Star(BookmarkManager mgr)
{
key = new Random().Next(100).ToString();
Console.WriteLine("here you key:" + key);
Console.WriteLine("Star当为前线程为: " + Thread.CurrentThread.GetHashCode());
mgr.Add(new Bookmark("read",CountionAt));
}
void CountionAt(Bookmark resumed)
{
Thread.Sleep(new TimeSpan(0, 0, 2));
string s = resumed.Payload as string;
BookmarkManager mgr = resumed.BookmarkManager;
mgr.Remove(resumed);
Console.WriteLine(DateTime.Now.ToString() + "CountionAt当为前线程为: " + Thread.CurrentThread.GetHashCode());
if (key.Equals(s))
Console.WriteLine("hello, will come!");
}
}
class Program
{
static void Main(string[] args)
{
//创建书签管理器
BookmarkManager mgr = new BookmarkManager();
OpenSesame pg = new OpenSesame();
Console.WriteLine("Main当为前线程为: " + Thread.CurrentThread.GetHashCode());
//填入书签
pg.Star(mgr);
//.........
//
string str = Console.ReadLine();
//调用书签
mgr.Resume("read", str);
Console.WriteLine("Main当为前线程为: " + Thread.CurrentThread.GetHashCode());
//.....
Thread.Sleep(new TimeSpan(0, 0, 5));
}
}
哈哈,我们终于完成我们的疑问1。但是我们的交互多了逻辑依然不是很清楚。就不能简单而清晰的保证控制流正确性。
上述代码是用于单个程序实例的,怎么来管理各种类似的程序呢?
1):需要可复用的组件来封装可恢复语句
2):想需要一个很好的控制流逻辑来控制它们。
动手看看再说:
namespace MyWorkflow
{
/// <summary>
/// 执行入口点
/// </summary>
[Serializable]
public abstract class Active
{
string _name;
public Active()
{
_name = "Active";
}
public Active(string name)
{
_name = name;
}
public string Name { get {return _name ;} }
public abstract void Execute(BookmarkManager mgr);
}
}
用Active代表交互行为,我们首先得封装交互的行为,把它自己作为书签,让后给他一个抽象Execute方法来执行处理。需要扩展书签管理
namespace MyWorkflow
{
public delegate void BookmarkLocation(Bookmark resumed);
[Serializable]
public class Bookmark
{
private string _Name;
private object _Payload;
private BookmarkManager _BookmarkManager;
private BookmarkLocation _CountinueAt;
public Bookmark() { }
public Bookmark(string name, BookmarkLocation countinueAt)
{
this._Name = name;
this._CountinueAt = countinueAt;
}
public string Name
{
get { return _Name; }
set { _Name = value; }
}
public BookmarkLocation CountinueAt
{
get { return _CountinueAt; }
set { _CountinueAt = value; }
}
public object Payload
{
get { return _Payload; }
set { _Payload = value; }
}
public BookmarkManager BookmarkManager
{
get { return _BookmarkManager; }
set { _BookmarkManager = value; }
}
}
public class BookmarkManager
{
private List<Bookmark> Bookmarks;
private List<Bookmark> internalBookmarks;
public BookmarkManager()
{
Bookmarks = new List<Bookmark>();
internalBookmarks=new List<Bookmark>();
}
public void Add(Bookmark bookmark)
{
if (bookmark != null)
{
bookmark.BookmarkManager = this;
Bookmarks.Add(bookmark);
}
}
public void Remove(Bookmark bookmark)
{
Bookmarks.Remove(bookmark);
}
internal void Resume(string bookmarkNmae, Object payload)
{
Bookmark mark = Bookmarks.Where(a => a.Name == bookmarkNmae).FirstOrDefault();
if (mark != null)
{
mark.Payload = payload;
//mark.CountinueAt.Invoke(mark);//直接调用
IAsyncResult result = mark.CountinueAt.BeginInvoke(mark, null, payload);
mark.CountinueAt.EndInvoke(result);
}
Bookmarks.Remove(mark);
}
public void Done()
{
foreach(Bookmark mark in internalBookmarks)
{
IAsyncResult result = mark.CountinueAt.BeginInvoke(mark, null, mark.Payload);
mark.CountinueAt.EndInvoke(result);
}
internalBookmarks.Clear();
}
public void RunActive(Active active, BookmarkLocation continueAt)
{
Bookmark mark = new Bookmark{ Name= active.Name, CountinueAt= continueAt, BookmarkManager=this, Payload=active };
internalBookmarks.Add(mark);
active.Execute(this);
}
}
}
接下来看看怎么实现以前的输入输出的交互呢
namespace MyWorkflow
{
public class Read : Active
{
public Read()
: base("Read") { }
private string _Text;
public string Text { get { return _Text; } }
public override void Execute(BookmarkManager mgr)
{
mgr.Add(new Bookmark(Name, null));
}
public void ContinueAt(Bookmark resumed)
{
_Text = resumed.Payload as string;
BookmarkManager mgr= resumed.BookmarkManager;
mgr.Remove(resumed);
mgr.Done();
}
}
class OpenSesame:Active
{
string key;
public override void Execute(BookmarkManager mgr)
{
key = new Random().Next(100).ToString();
Console.WriteLine("here you key:" + key);
mgr.RunActive(new Read(), null);
}
public void ContinueAt(Bookmark resumed)
{
Read read =resumed.Payload as Read;
if (read.Text == key)
Console.WriteLine("hello,will come");
}
}
}
到此我们实现了可恢复语句组件。不知道你看清楚了没有呢?呵呵。。放轻松,仔细看看,这里有点绕头啊。。
我们怎么去做控制流呢?呵呵,休息休息。请见下文。。