我有一个应用程序,它使用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()之前发生的实例存储交互。
答案 0 :(得分:2)
好吧,所以我的问题的答案似乎是否定的。在环境事务存在时,我找不到消除挂起的方法。我所做的是围绕每个实例存储交互(创建工作流所有者,持久化)和
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
{
// Method call that interacts with the instance store.
ts.Complete();
}
这允许这个结构开始工作流程:
CreateWorkflowOwnerCommand
以获取锁定。BeginRun
。缺点?如果在Persist之后抛出异常,则不会回滚;工作流程将在某个时刻运行。我有一些基础设施可以最大限度地减少问题,但长期的答案是找到一种方法来使用AppFabric(此时不可能,因为需要更改规模,但将会查看下一个版本)。
答案 1 :(得分:0)
工作流可以在事务中运行,但使用WorkflowApplication执行此操作需要更多工作。例如,工作流实际上在后台线程上执行,而不是在您创建TransactionScope的线程上执行。如果您使用WorkflowInvoker,它将在同一个线程上作为同一事务的一部分运行。
要记住的另一件事是工作流运行的时间。当然,这取决于您的工作流程,但通常工作流程会在较长时间内运行,并且保持交易活动并不是一个好的计划。
在工作流程中,您可以使用TranscationScopeActivity。基本上是一个在TransactionScope中包装许多子活动的活动。它不会暴露常规的TransactionScope所做的一切,但是非常有用。
但是,对于长时间运行的工作流程,更好的模型是使用CompensableActivity和可补偿的事务。
答案 2 :(得分:0)
默认情况下,WorkflowApplication在与主机线程不同的线程上运行工作流。 Ambient事务只能由主机线程上运行的代码访问。您不能跨线程共享Ambient事务。
您可以通过设置SynchronizationContext让WorkflowApplication使用调用线程。但更好的解决方案是让每个工作流实例管理自己的事务。如果存在事务范围,则在事务下将发生持久性。工作流始终在事务边界处持续存在。