WorkflowApplication的实例存储交互可以参与环境事务吗?

时间:2011-07-28 17:39:26

标签: c# .net workflow-foundation-4

我有一个应用程序,它使用WorkflowApplication执行工作流程以及执行其他操作。在调用BeginRun()之前,我希望这些其他内容和WorkflowApplication与实例存储的交互由一个事务限定,因此我将所有内容都包装在一个中。但是,引入此事务会导致诸如WorkflowApplication.Persist()之类的调用挂起。以下示例是基本实例持久性示例的修改版本(描述为here,从here下载)。我通过确定事务中完成的所有内容来修改原始内容;这非常模仿我的应用程序。

using System;
using System.Activities;
using System.Activities.DurableInstancing;
using System.Activities.Statements;
using System.Runtime.DurableInstancing;
using System.Threading;

namespace Microsoft.Samples.Activities
{
  using System.Transactions;

  class Program
  {
    static AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
    static Activity activity = CreateWorkflow();
    static Guid id;

    const string readLineBookmark = "ReadLine1";

    static void Main()
    {
      StartAndUnloadInstance();
      LoadAndCompleteInstance();

      Console.WriteLine("Press [Enter] to exit.");
      Console.ReadLine();
    }

    static void StartAndUnloadInstance()
    {
      using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        WorkflowApplication application = new WorkflowApplication(activity);
        InstanceHandle handle;
        application.InstanceStore = SetupInstanceStore(out handle);

        application.Completed = (e) =>
          { handle.Free(); };

        application.PersistableIdle = (e) =>
            {
              return PersistableIdleAction.Unload;
            };

        application.Unloaded = (e) =>
            {
              instanceUnloaded.Set();
            };

        application.Persist();
        id = application.Id;
        application.Run();
        ts.Complete();
      }

      instanceUnloaded.WaitOne();
    }

    static void LoadAndCompleteInstance()
    {
      using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        string input = Console.ReadLine();

        WorkflowApplication application = new WorkflowApplication(activity);
        InstanceHandle handle;
        application.InstanceStore = SetupInstanceStore(out handle);

        application.Completed = (workflowApplicationCompletedEventArgs) =>
          {
            handle.Free();
            Console.WriteLine(
              "\nWorkflowApplication has Completed in the {0} state.",
              workflowApplicationCompletedEventArgs.CompletionState);
          };

        application.Unloaded = (workflowApplicationEventArgs) =>
          {
            Console.WriteLine("WorkflowApplication has Unloaded\n");
            instanceUnloaded.Set();
          };

        application.Load(id);

        //this resumes the bookmark setup by readline
        application.ResumeBookmark(readLineBookmark, input);
        ts.Complete();
      }

      instanceUnloaded.WaitOne();
    }

    static Sequence CreateWorkflow()
    {
      Variable<string> response = new Variable<string>();

      return new Sequence()
      {
        Variables = { response },
        Activities = { 
                        new WriteLine(){
                            Text = new InArgument<string>("What is your name?")},
                        new ReadLine(){ 
                            BookmarkName = readLineBookmark, 
                            Result = new OutArgument<string>(response)},
                        new WriteLine(){
                            Text = new InArgument<string>((context) => "Hello " + response.Get(context))}}
      };
    }

    private static InstanceStore SetupInstanceStore(out InstanceHandle handle)
    {
      SqlWorkflowInstanceStore instanceStore =
          new SqlWorkflowInstanceStore(@"Data Source=.;Initial Catalog=SampleInstanceStore;Integrated Security=True;Asynchronous Processing=True");

      handle = instanceStore.CreateInstanceHandle();
      InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));

      instanceStore.DefaultInstanceOwner = view.InstanceOwner;

      return instanceStore;
    }
  }
}

using System;
using System.Activities;
using System.Activities.DurableInstancing;
using System.Activities.Statements;
using System.Runtime.DurableInstancing;
using System.Threading;

namespace Microsoft.Samples.Activities
{
  using System.Transactions;

  class Program
  {
    static AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
    static Activity activity = CreateWorkflow();
    static Guid id;

    const string readLineBookmark = "ReadLine1";

    static void Main()
    {
      StartAndUnloadInstance();
      LoadAndCompleteInstance();

      Console.WriteLine("Press [Enter] to exit.");
      Console.ReadLine();
    }

    static void StartAndUnloadInstance()
    {
      using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        WorkflowApplication application = new WorkflowApplication(activity);
        InstanceHandle handle;
        application.InstanceStore = SetupInstanceStore(out handle);

        application.Completed = (e) =>
          { handle.Free(); };

        application.PersistableIdle = (e) =>
            {
              return PersistableIdleAction.Unload;
            };

        application.Unloaded = (e) =>
            {
              instanceUnloaded.Set();
            };

        application.Persist();
        id = application.Id;
        application.Run();
        ts.Complete();
      }

      instanceUnloaded.WaitOne();
    }

    static void LoadAndCompleteInstance()
    {
      using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        string input = Console.ReadLine();

        WorkflowApplication application = new WorkflowApplication(activity);
        InstanceHandle handle;
        application.InstanceStore = SetupInstanceStore(out handle);

        application.Completed = (workflowApplicationCompletedEventArgs) =>
          {
            handle.Free();
            Console.WriteLine(
              "\nWorkflowApplication has Completed in the {0} state.",
              workflowApplicationCompletedEventArgs.CompletionState);
          };

        application.Unloaded = (workflowApplicationEventArgs) =>
          {
            Console.WriteLine("WorkflowApplication has Unloaded\n");
            instanceUnloaded.Set();
          };

        application.Load(id);

        //this resumes the bookmark setup by readline
        application.ResumeBookmark(readLineBookmark, input);
        ts.Complete();
      }

      instanceUnloaded.WaitOne();
    }

    static Sequence CreateWorkflow()
    {
      Variable<string> response = new Variable<string>();

      return new Sequence()
      {
        Variables = { response },
        Activities = { 
                        new WriteLine(){
                            Text = new InArgument<string>("What is your name?")},
                        new ReadLine(){ 
                            BookmarkName = readLineBookmark, 
                            Result = new OutArgument<string>(response)},
                        new WriteLine(){
                            Text = new InArgument<string>((context) => "Hello " + response.Get(context))}}
      };
    }

    private static InstanceStore SetupInstanceStore(out InstanceHandle handle)
    {
      SqlWorkflowInstanceStore instanceStore =
          new SqlWorkflowInstanceStore(@"Data Source=.;Initial Catalog=SampleInstanceStore;Integrated Security=True;Asynchronous Processing=True");

      handle = instanceStore.CreateInstanceHandle();
      InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));

      instanceStore.DefaultInstanceOwner = view.InstanceOwner;

      return instanceStore;
    }
  }
}

运行时,应用程序挂起在StartAndUnloadInstance()中的Persist()调用上。大约五分钟后挂起结束(某处有超时)。挂起时,您可以通过查看SQL Server中的活动监视器来查看原因。在SetupInstanceStore()中创建工作流所有者的调用获取对LockOwnersTable中的行的独占锁。对Persist()的调用尝试获取相同的锁。糟糕。

无论如何,更广泛的问题如标题所述:在调用BeginRun()之前,实例是否可以存储我在环境事务上下文中工作的交互?如果是这样,我做错了什么?如果没有,我有什么选择?到目前为止,我已经能够通过围绕WorkflowApplication访问实例存储的每个案例来消除挂起/死锁:

using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
{
  // Method call that interacts with the instance store.
  ts.Complete();
}

这让我前进了一些,但是我减少了编写补偿代码来处理这些调用失败的问题。这不应该是必要的。非常感谢见解。

编辑:为了进一步说明,我希望工作流在与其他数据库活动相同的事务中执行之前保持不变。我希望这样:

  • 如果在执行工作流程期间主机进程终止,则允许重新运行工作流。
  • 确保回滚持久性,如果其他数据库活动失败,则永远不会运行工作流。

工作流本身不需要在事务中运行,只需要在调用BeginRun()之前发生的实例存储交互。

3 个答案:

答案 0 :(得分:2)

好吧,所以我的问题的答案似乎是否定的。在环境事务存在时,我找不到消除挂起的方法。我所做的是围绕每个实例存储交互(创建工作流所有者,持久化)和

using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
{
  // Method call that interacts with the instance store.
  ts.Complete();
}

这允许这个结构开始工作流程:

  • 启动顶级工作单元(NHibernate会话)。
    • 做点什么。
    • 获取实例存储句柄,执行CreateWorkflowOwnerCommand以获取锁定。
    • 创建工作流程应用程序。
    • 坚持工作流程。
    • 致电BeginRun
  • 提交。

缺点?如果在Persist之后抛出异常,则不会回滚;工作流程将在某个时刻运行。我有一些基础设施可以最大限度地减少问题,但长期的答案是找到一种方法来使用AppFabric(此时不可能,因为需要更改规模,但将会查看下一个版本)。

答案 1 :(得分:0)

工作流可以在事务中运行,但使用WorkflowApplication执行此操作需要更多工作。例如,工作流实际上在后台线程上执行,而不是在您创建TransactionScope的线程上执行。如果您使用WorkflowInvoker,它将在同一个线程上作为同一事务的一部分运行。

要记住的另一件事是工作流运行的时间。当然,这取决于您的工作流程,但通常工作流程会在较长时间内运行,并且保持交易活动并不是一个好的计划。

在工作流程中,您可以使用TranscationScopeActivity。基本上是一个在TransactionScope中包装许多子活动的活动。它不会暴露常规的TransactionScope所做的一切,但是非常有用。

但是,对于长时间运行的工作流程,更好的模型是使用CompensableActivity和可补偿的事务。

答案 2 :(得分:0)

默认情况下,WorkflowApplication在与主机线程不同的线程上运行工作流。 Ambient事务只能由主机线程上运行的代码访问。您不能跨线程共享Ambient事务。

您可以通过设置SynchronizationContext让WorkflowApplication使用调用线程。但更好的解决方案是让每个工作流实例管理自己的事务。如果存在事务范围,则在事务下将发生持久性。工作流始终在事务边界处持续存在。