返回首页

简介
对Windows Workflow Foundation(WF)的可扩展性的主要观点是类System.ComponentModel.Actvity。这是微不足道的继承Activity类,重写Execute方法,并添加自己的行为。然后,您可以组合成一个工作流的新的自定义活动。然而,工作流引擎设计,以支持这个配方不仅速度快的活动:不支持封锁活动或长时间运行的活动。这一类的活动,需要多做一些工作。
不幸的是,推荐的方式来写一个长时间运行的活动是相当有点更多地参与。活动必须告知一个后台线程来执行的活动,然后让工作流引擎知道这将是一段时间的活动完成之前,最后,当背景工作完成通知工作流引擎。工作流运行时的相互作用驱动,在很大一部分的要求,它有可能写的工作流程到一个持久性存储的特定的接口。松散,所接受的方法来做到这一点是如下:System.Workflow.Runtime.WorkflowQueuingService创建活动的Execute方法,并让运行时知道应该在这个活动中的另一种方法处理到达此队列的数据。此队列将使用到船通知和数据从外部线程活动。使用System.Workflow.Runtime.WorkflowRuntimeService或其他一些外部代理开始在不同的线程长时间运行的操作,甚至在一个单独的进程。告诉工作流运行时,这一活动正在暂停,等候从队列结果通知。一旦完成长时间运行的活动,其结果是放置在步骤1中创建的队列。WF运行时通知队列上的数据和唤醒工作流的活动,并呼吁在步骤1中指定的回调方法。该活动unqueues的结果,也许对他们的行为,并然后让工作流运行时知道这个活动是完成(或继续等待更多的数据)。
注意:步骤3和4之间的时间可以是相当大的。例如,如果您的活动向用户发送电子邮件请求,它可能是天!此外,事情就变得棘手,如果涉及到工作流持久性。例如,第3步后,WF运行时可能会写的工作流程持久存储,并从内存中卸载。因此,在第4步,各种活动变量可能无法使用!!要做到这一点,正确的活动作者将不得不作出的所有活动的变量的副本,并确保他们是正确的缓存来解决这个问题。不用说,有相当数量的样板代码需要。
本文包含的示例代码点是自动化这些步骤照顾锅炉板代码??照顾封送活动参数需要长期运行的代码,因此,它是免疫被卸载活动的持久性机制的数据;管理的活动,并通过WorkflowQueuingService自动运行代码之间的通信;,并提供一些重新启动长时间运行的操作的支持,如果WF主机重新启动程序和工作流程是从持久性存储加载。
仍然是工作,需要做的事情,因为下面是一些锅炉板,但它是简单得多。在这个项目中所包含的库,例如,意味着你永远需要在上述步骤1创建的队列。
究其原因,WF运行时不处理长时间运行的活动,自然是在它的执行引擎的设计。它不旋转,每个Activity.Execute方法调用的一个新的线程。因此,如果这些活动中使用线程,这些线程不能被用来执行其他工作流或工作流活动:可扩展性成为一个问题。此外,具有持久性的互动 - 尤其是如果一项活动是为天运行 - 将非常棘手。背景和具体要求
有描述如何编写一个长期运行的活动的例子还有很多。我学到了什么,我从一个{A}我是约10这些长时间运行的活动所需的书面知道,所以我决定需要一个更通用的方法。
我希望代码能够处理几种不同类型的长时间运行的活动:一个计算,需要很长的时间来运行,但运行在相同的工作流过程。例如,旋转1000的图像。制作网页查询,可能需要几分钟时间才能完成运行外部程序,可能需要10-12小时才能完成。
此外,我想的活动对强劲的工作流崩溃运行时和重新启动作为主机(即一旦有我的服务和运行我不想需要去触摸它!)。如果外部程序正在执行的WF主机重新启动,我不希望它试图重新运行外部程序,但只是继续等待其结果。另一方面,如果发生了一台机器重新启动,那么我也希望外部程序自动重新启动。如果长时间运行的计算是由主机重新启动中断或外部Web请求被打断,我想他们与WF主机重新启动了备份。长时间运行的工作流活动应全力支持工作流运行时所使用的持久性接口。请尽可能简单的编码。持久性的要求,实际上,增加了相当的复杂性有点。虽然我认为我现在已经是简单得多还有一些锅炉板代码,因为这(见下文)书面。
一件事,应该指出的是没有一个主要的需求是对工作流取消的强劲。我不认为这是太难实现,但我没有测试。最后,支持长期运行的任务的异步方法是完全支持在工作流运行时开箱。在此方法中,程序员使用一个活动发起一个长期运行的后台线程(或一个Web请求,等等)的活动。工作流程,然后移动到活动等待,一旦发生火灾。该事件包含从外部活动所产生的数据。这种方法是不充分的探讨,因为在执行重新启动行为和可重用性的感知困难。但是,我们没有理由这里介绍的设计不能实施与使用回调和外的现成的一些工作活动。使用代码
代码下载中包含三个项目:CommonWFLibrary - 包含所有的基类和LongRunningActivityBase工作流活动的一些具体的实现。CommonWFLibraryTest - WF活动的单元测试。在这个项目中可以找到大量的例子,如何使用和延长LongRunningActivityBase活动。RunAndPauseWithArg - CommonWFLibraryTest ExternalPorgramActivity活动的单元测试中使用的外部程序。
的CommonWFLibrary包含一个类,其使用下面演示。一个快速大纲提供了有益的指导,为我们写一些简单的的示例代码:LongRunningActivityBase - 任何长时间运行的活动应该从这个类继承。其实有没有方法来覆盖。比较新的方法定义,然后打上属性,在LongRunningActivityBase拿起。ExternalProgramActivity - 外部程序运行时,此活动将运行外部程序和暂停的WF。如果重新启动工作流宿主和外部程序仍在运行的活动将等待,直到该实例完成。否则重新启动外部程序。RunLongrunningWorkflow - 运行此活动第二次工作流程,并等待它完成。由于预计将执行工作流持久性,这一活动也很少,重新启动主机时。
让我们的工作重点的LongRunningActivityBase,并延长它。首先,一个很简单的活动,只是睡10秒。

class DelayFor10Seconds : LongRunningActivityBase

{

  [LongRunningMethod]

  public static void Run()

  {

    Thread.Sleep(10*1000);

  } 

}

这就是它!当此工作流活动是由工作流运行时遇到时,它会在ThreadPool线程上运行上面的Run方法,并允许工作流运行时,继续与其他任务!
LongRunningActivityBase扩展机制是通过CodeAttributes,像LongRunningMethod。有几个非常重要的事情要注意LongRunningActivity的方法签名(尤其是):该方法是静态的 - 这是因为卸载的工作流程可长时间运行的进程在后台线程开始执行的时间。有没有保证的实例变量将于。这并不适用于一般其他扩展方法介绍如下。方法是公共的 - 这是由于某些NET反射的要求。目前,这适用于下面每一个扩展方法。
我呼吁那些出明确的,因为这不会工作,如果不为这个特殊的方法的情况下!将抛出一个例外,如果你LongRunningMethod的方法不是公共的和静态!
接下来,这是值得采取快速浏览一下代码,设置了工作流程主机。除了一般的WF运行时启动,我们也有运行时添加LongRunningActivityBaseService服务。这LongRunningActivityBase使用这项服务,协调这些长期运行活动。{C}
RegisterService方法是提供一个方便 - 你没有打电话,如果你不想。如果你是在持久性感兴趣,那么你会修改如下RegisterService呼叫 - 以及加入了工作流运行时的持久性服务:
  DirectoryInfo cache = new DirectoryInfo("c:\wfcache");

  runtime.AddService(new FilePersistenceService(true, cache));

  LongRunningActivityBinaryPersister lrp = new LongRunningActivityBinaryPersister(

      new FileInfo(cache.FullName + "\\longrunning"));

  LongRunningActivityBase.RegisterService(runtime, obj => lrp.Save(obj),

      () => lrp.Restore());

  runtime.StartRuntime();

对于一个长期运行的活动的工作流程得到充分的持久性,必须提供两种类型的持久性。首先是平时的工作流运行时的持久性服务。在这种情况下我用FilePersistenceSevice。此代码的Windows Workflow Foundation SDK代码由微软提供的样品,被拉到。写出一个工作流程的所有活动的变量和状态,因为它是写出来的,这将需要照顾。下面是配置写出来的工作流程,每次进入空闲状态。这正是国家,长时间运行的活动后进入LongRunningActivityBase启动的背景方法。不幸的是,LongRunningActivityBaseService也有状态必须保存,如果它是从主机崩溃恢复。 LongRunningActivityBaseService需要两名代表,负责写作,州和读回。每次更改后的状态写出来,但只有读回时开始工作流宿主 - 在任何时候都保持其在内存中的状态(见下文附注)。 LongRunningActivityBinaryPersister是其实很简单,很容易被替换数据库的代码或任何需要保存主机状态。
上面的代码是一个配方。有许多不同的方式来完成同样的事情 - 他们往往取决于如何实际执行的工作流宿主。请参阅下面的一个简短讨论如何正确地实施在WCF服务,例如。
DelayFor10Seconds是一个非常枯燥的长时间运行的活动更糟的是,不是所有的有用!更有趣的是有一个活动的输入和输出参数。这就要求延长了一下前面的例子:
class DelayForSeconds : LongRunningActivityBase

{

  public int Ticks { get; set; }

 

  public string Message {get; set; }

 

  public DelayForSeconds ()

  {

  }

 

  [Serializable]

  public class Args {

    public int _time;

  }

 

  [LongRunningGatherArguments]

  public Args GatherArgs()

  {

    Args a = new Args();

    a._time = Ticks;

    return a;

  }

 

  [LongRunningMethod]

  public static Result Run(Args a)

  {

    Thread.Sleep(a._time*1000);

    Result r = new Result();

    r._message = "Done waiting for " + a._time.ToString() + " seconds.";

    return r;

  }

 

  [Serializable]

  public class Result {

    public string _message;

  }

 

  [LongRunningDistributeArguments]

  public void DistributeArgs(Result r)

  {

    Message = r._message;

 

}

有少比这里的代码量就意味着:刚才我提到的事实,我是无法消除所有的样板代码。那么,它在这里!有工作流引擎执行它时,会发生这一活动的步骤: GatherArgs方法是先叫"marshalsquot需要长时间运行的活动上运行的后台线程的数据。如前所述,也不能保证当背景的方法执行,因此可以不依赖于实例变量,不幸的是,这项活动将在内存中。请注意,你的类,它包含封送的对象必须是可序列化的。LongRunningActivityBase然后保存这些参数的封送参数和队列在ThreadPool工作项目。它讲述了工作流引擎,现在是空闲等待完成其他一些工作。后台工作项目与封送参数调用的Run方法。当运行方法是完成结果对象又是序列化(注同样的要求!),并通知工作流运行时,长时间运行的任务已经完成,被唤醒和工作流程显示。回到积极的工作流程中,在DistributeArgs被称为quot; unmarshalquot;从长时间运行的活动结果返回到活动的实例变量。
有几件事情,这里要注意:收集和分发的方法是实例方法,而不是静态方法。他们还像以前那样公开。你给他们什么名字并不重要,只要他们Attribute'd适当。在收集签名的回报。NET CLR类型作为参数传递给Run方法。运行必须返回相同类型的分配方法作为参数传递给。违反这些规则可能会导致一种无声的故障,在当前代码。所以一定要确保单元测试!如果您的活动一直没有结果,是无可厚非的Run方法返回void,然后删除从您的代码的分发方法。它也是优良的Run方法接受任何参数,但返回结果。在这种情况下,你可以从你的代码下降的收集方法。必须标记为Serializable。NET CLR中的参数和结果类型,如上所述!!如果没有,你就会在运行时的错误 - 他们很难追查什么原因造成的问题。当我编码我10长时间运行的活动,这是我遇到的最令人沮丧的错误。往往会得到错误消息运行时吞噬。我敢肯定有一些可用性的改进可以实施,以帮助这个。
我们现在也可以解释为什么LongRunningActivityBaseService必须能够坚持数据。如果工作流宿主崩溃,而长时间运行的活动过程中,必须重新启动。这意味着再次调用LongRunningMethod方法。而如果LongRunningMethod需要的论点,他们必须被缓存某处。
,是最后一个用例的目的是要处理的LongRunningActivityBase。考虑的是运行一个外部程序,可能需要5个小时才能完成的活动。我们希望的活动,以防止从工作流程出发,直到外部程序完成。此外,如果工作流主机重新启动(说这是在Web服务托管),4.5小时后,它会很好的,不重新启动程序 - 而只是等待它完成在30分钟内和工作流程,拿起如果没有出现问题。
为了要做到这一点,活动将能够跟踪类似的外部正在运行的程序的PID。这样,当它被恢复,它可以查询系统进程是否仍在运行并等待它,或重新启动它,如果它不再运行。 ExternalProgramActivity在示例代码中提供的活动,正是这一点。下面是肉的代码(包括完整的源代码,在下载):
  [LongRunningMethodStarter]

  static public EPContext RunExternalProgram(LongRunningContext context,

      EPStartupInfo start, EPContext lastTime)

  {

    ProcessController pc = null;

    ProcessStartInfo pinfo = new ProcessStartInfo(start._path);

    pinfo.Arguments = start._arguments;

    pinfo.CreateNoWindow = false;

    pinfo.ErrorDialog = false;

    pinfo.UseShellExecute = false;

    if (lastTime == null)

    {

      pc = new ProcessController(pinfo, context);

    } else {

      pc = new ProcessController(lastTime, pinfo, context, start._can_restart);

    }

    return pc.Context;

  }

这是主要的运行方式。以前,我们与LongRunningMethod标签。为了表示不同的行为,我们现在与LongRunningMethodStarter标记。这告诉LongRunningActivityBaseService,它应该被称为每次主机重新启动。了解如果这是一个重新启动或首次调用参数lastTime。这种方法被称为第一次将空。注意RunExternalProgram方法的返回类型是lastTime的类型相同。如果工作流的主机崩溃并重新启动,将再次调用此方法,不管它返回首次。在方法体中的if语句决定在这段代码中发生的。类lastTime的内容完全取决于你。然而,它必须是可序列化的。
本实施下半年的代码是如何通知工作流宿主进程已完成执行。这里的关键是LongRunningContext对象。注意上下文对象是藏匿在ProcessController对象。该对象设置一个过程执行完毕时触发的回调:
LongRunningContext _context; 



void _p_Exited(object sender, EventArgs e)

  {

    EPResult r = new EPResult();

    r._result = _p.ExitCode;

    _context.Complete(r);

  }

实例变量_context ProcessController对象的构造。完整的方法是所谓的结果对象。记住保持,在分发方法必须有同类型作为quot参数rquot;以上,以便它被称为所以,在长期运行活动的结果可以被放置在活动的属性返回和其余工作流的!
这种模式的基础所有使用我所描述的模式。每个人只是这个更为普遍的专业化。其他实施和使用说明
如果你开始的工作流程,其中包含一个WCF服务的LongRunningActivityBase活动,它可以是一个小技巧,以确保你的持久性和LongRunningActivityBaseService持久性服务的正常运行。下面是一些示例代码,我已经习惯了创建WCF服务LongRunningActivityBaseService。实际位置实际上是抓住从app.config文件的名称 - 值的部分。
public class DTLongRunningPersistance : LongRunningActivityBaseService

{

  private static FileInfo GetFileInfo()

  {

    string pdir = ConfigurationManager.AppSettings["PersitanceDirectory"];

    return new FileInfo(pdir + "\\longRunningActivityInfo.bin");

  }

 

  private static LongRunningActivityBinaryPersister lrp =

    new LongRunningActivityBinaryPersister(GetFileInfo());

 

  public DTLongRunningPersistance()

    : base(obj => lrp.Save(obj), () => lrp.Restore())

  {

  }

}

,然后在app.config文件,我在WorkflowRuntime的XML标记添加下面的XML代码:
<system.serviceModel>

  <services>



    ...

  </services>

  <behaviors>

    <serviceBehaviors>

      <behavior name="DeepTalkRenderService.Workflow1Behavior">

        <serviceMetadata httpGetEnabled="true" />

        <serviceDebug includeExceptionDetailInFaults="true" />

        <serviceCredentials>

          <windowsAuthentication allowAnonymousLogons="false"

             includeWindowsGroups="true" />

        </serviceCredentials>

        <workflowRuntime>

          <services>

            <add type="DeepTalkRenderService.Services.DTLongRunningPersistance,

                DeepTalkRenderService"/>

            <add type="DeepTalkRenderService.Services.DTRPersistance,

                DeepTalkRenderService"/>

          </services>

        </workflowRuntime>

      </behavior>

    </serviceBehaviors>

  </behaviors>

</system.serviceModel>

类似的代码,以及用于创建FilePersitancy类。限制和今后可能的方向
有一些变化,将直接影响这项工作的。NET 4.0中的几个。具体来说,将有可能保持活动的实例变量在内存中的固定而长时间运行的活动是执行。这可能避免需要的参数和结果的封送,这将是一个代码样板的复杂性显着减少。这是从PDC谈判没有明确,但是,如果你不得不放弃某些类型的持久性,在这种情况下也没有,如果这迫使你保持在内存中的整个工作流程。
理想的形式(在我心中)将像一个iterator。在第一部分中,在工作流活动的情况下运行,将收集所需的参数,然后调用yield。然后,它将恢复执行后台线程 - 但在第一步中编写的任何局部变量仍然是有关。再次,完成后会产生。最后,它会被称为在现场的工作流程范围内。即使这是可能的,这将是非常错误的程序员容易出现:所有的工作流程参数将保持在范围根据IDE和编译器,但在运行时碰不得。这些错误将是可怕的梳理!这有点像一个后台线程修改GUI元素时!另一种可能性是为纪念工作流程活动,你想可和代码会做一些反思,以确保他们保存的变量。然而,这几个问题,其中之一就是继承的属性,就很难处理。
一个工作流的设计,此代码限制是移动工作流从主机到主机的能力非常不错的功能:你可以坚持上机工作流A,然后启动机器B上运行,因为一切有关写出来,它不应该有任何问题。然而,LongRunningActivityBaseService的状态持久性突破:国家不再与工作流一起进行。要解决此问题的服务状态都必须坚持活动状态。直到晚在编码过程中,我意识到了这一点限制,所以这不是一个初步设计要求。
在同一行双实​​施的国家还有另一个潜在的问题。服务写入其状态,每次在内存中的状态变化。然而,工作流程,可以写出不同的时间表及其状态。我所有的测试总是写工作流空闲时的状态。这是很好的匹配:这几乎是唯一的一次,服务状态的变化。然而,如果有不同的持久性的时间表,这可能意味着磁盘上的状态不同步,工作流宿主,如果在这一分钟崩溃,它可能会导致一些混乱,重新启动主机时。在写这篇文章,这可能限制被发现和尚未探索所有。
最后一个限制是所有的封送的对象(参数,结果和上下文类)的序列化的要求。这是有传染性的困难是:不仅要标明你的类序列化,但你的类中包含的每一个类也必须同样显着!这是一个现实的问题,如果您尝试存储一个第三方库对象。但是,即使在自己的库,你可以找到自己修改大量被标记为Serializable的结构。 ,他们是公众的要求也可以是一个潜在的问题,虽然不是经常。历史
1.0版 - 2008年12月8日,日 - 初步写作| gordonwatts

回答

评论会员:游客 时间:2011/12/28
您好戈登,我感到非常高兴地看到UR文章。而这,正是我一直在寻找。但是,该代码是没有download.It说,移动源。可以请帮助
?gordonwatts
评论会员:游客 时间:2011/12/28
当我点击下载源链接,为什么要让我登录。一旦做到这一点,我可以再次点击链接在顶部,它问我在哪里保存zip文件。因此,似乎喜欢它工作得很好,对我来说......{S0}再次尝试,让我知道。欢呼声,戈登
。Su_shamim
评论会员:游客 时间:2011/12/28
这是一个很好的的atticle。但不能运行程序。当我点击登录按钮,follwing消息显示难道不能找到存储过程'RetrieveNonblockingInstanceStateIds"
。gordonwatts
评论会员:游客 时间:2011/12/28
登录按钮?我不记得有登录"按钮,在示例应用程序的任何地方...对不起!欢呼声,戈登
MR_SAM_PIPER
评论会员:游客 时间:2011/12/28
你已经做了很多这里的工作非常有趣的-但我不知道你的方法实现,不能与本地的数据交换服务模拟。如果我想从一个长时间运行的任务,在工作流实例,然后空闲等待完成这一任务,我会用CallExternalMethod活动,随后直接由HandleExternalEvent活动。外部方法的实施,将派遣异步使控制立即返回到工作流实例的工作,届时,将闲置,等待外部事件到达。对外工作一旦完成,外部方法,提高工作流的事件,预计HandleExternalEvent活动的签名相匹配的。任何结果所需的信息传递到工作流发送作为事件参数的一部分。这分离出来执行任务的工作流程,通过一个众所周知的服务接口,并保持工作的实施轻盈而简单。我道歉,如果我完全缺少点,但已经是相当复杂的工作流程的基础,我很想知道,这个解决方案的好处是什么已经可以在框架
。gordonwatts: |呀 - 这是一个很好的的问题。我们没有理由不能使用外部上述讨论的方法完成同样的事情。事实上,当我尝试的事情,我的项目,这是我原来使用的。

但我有这种做法了不少麻烦。有很多的样板代码分布在几个不同的源文件。应该在一起(如活动变量和使用他们的代码)实际上是在不同的文件。
,我不得不重复很多代码,每次我创建了一个新的活动(在我工作的项目中,我为了这些长期运行的活动10)。
而一旦你有你必须支持这些外部方法调用(或恢复等)以及持久性。我的框架已经建立在一个模式,如果这种模式你的任务,那么它使事情变得简单了很多。没有防止你做呼叫/处理方式写一个类似的模式,当然!

这是如何,我想出了上述办法。它使代码关闭它是用来。还有更boilderplate代码比我想的(特别显式代码元帅/活动参数拆封),但至少它是在同一个地方。

最后,我以前写的代码没有长时间运行的方法的事件。因此,这种模式意味着我能避免写一个单独的线程中的方法完成时,会触发一个事件每一个长时间运行的活动。当然,它应该是可能的代码与此类似,这种优势的一个框架。

我的代码实际上是相当类似你所描述的去耦。在长时间运行的活动代码是负责活动参数,将在适当的形式,方法调用,方法调用。当调用返回时,它无论是需要的结果,并发送回。因此,真正的代码是从WF代码本身脱钩,并处理长期运行方面的代码的代码中分离出来。有一些地方的代码混合的情况下,。例如,我有一个长期运行的活动,删除目录(我要删除的目录可以包含超过30万的小图像文件) - 那么就没有很多去耦。

你是对复杂的权利。我真的很喜欢WF,而现在,我试着做somethign轻度困难中,我想我明白了为什么他们提出一些他们所做出的设计决策的很多。当我还是一个初学者常常觉得像这些方式获得。我仍然认为他们可以做一些工作做出愚蠢的事情简单的API。

这让我想起我的最后一点。我是一个相对的初学者,当谈到白表。这项工作是我的爱好,不是我真正的工作(虽然我刚刚发现,我应该给谈项目,这是连接,sheesh)。因此可能有其他更简单的解决方案。阅读这篇文章,让其他人知道其他方法(如呼叫/处理外部方法/事件),人们应该随时在这里建议!

欢呼声,
戈登
评论会员:。MR_SAM_PIPER 时间:2011/12/28
响应。这里的一个真的很好的案例研究,在多线程并行使用WF,说明使用异步调用相关CallExternalMethod / HandleExternalEvent的方法。不幸的是,并非所有的代码都提供,但非常详细的讨论:

{A3}

希望它有助于
评论会员:。gordonwatts 时间:2011/12/28
这是一个伟大的文章。我只做了一个快速阅读(更多的休息后,我猜)。真的解释是怎么回事内WF的。他们针对"取消"的问题,我不,但他们似乎没有明确处理persistancy的问题(​​至少不会在文章中)。

方法看起来几乎相同的地雷,除非他们使用的WF队列进行通信,而不是外部活动。从这个意义上说,我认为我的做法是不太复杂。我没有时间来下载代码,但它看起来像他们可能具有相同的元帅,/我的unmarshal困难。此外,我不清楚artibrary参数/结果传回之间来回活动和外部服务。

感谢为指针。我将下载的代码,当我有机会看一看。我一定要添加这篇文章作为参考,它解释WF的,我目的跳过内部的一个很好的工作。

欢呼声,
戈登
评论会员:。 时间:2011/12/28