I am currently experimenting with Workflow Foundation 4.5 in a client, WCF server architecture.
The way i set up the server is by creating a regular WCF Service Application (not a WCF Workflow Service Application) and added a bunch of web methods to start, resume, get states of of entities living in the workflow.
So i have things like :
public class WebService1 : WebService
{
private const string ConnectionString = "Server=.\\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI";
private SqlWorkflowInstanceStore _store;
public SqlWorkflowInstanceStore Store
{
get
{
if (_store == null)
{
Debug.WriteLine("Building new store");
_store = new SqlWorkflowInstanceStore(ConnectionString);
StateMachineStateTracker.Promote(Store);
}
return _store;
}
}
[WebMethod]
public Guid Start(Guid resourceId)
{
var activity = new Activity1(); // My state machine
var parameters = new Dictionary<string, object> { { "resourceId", resourceId } };
var wfApp = new WorkflowApplication(activity, parameters, new WorkflowIdentity("v1", new Version(1, 0), string.Empty));
ConfigureWorkflowApplication(wfApp);
wfApp.Run();
return wfApp.Id;
}
[WebMethod]
public void Resume(Guid instanceId, string bookmarkName, object bookmarkParameter)
{
var activity = new Activity1();
var wfApp = new WorkflowApplication(activity, new WorkflowIdentity("v1", new Version(1, 0), string.Empty));
ConfigureWorkflowApplication(wfApp);
wfApp.Load(instanceId);
wfApp.ResumeBookmark(bookmarkName, bookmarkParameter);
}
[WebMethod]
public string GetCurrentState(Guid instanceId)
{
var activity = new Activity1();
// Things get messy here :
// var instance = WorkflowApplication.GetInstance(instanceId, Store);
// var wfApp = new WorkflowApplication(activity, instance.DefinitionIdentity);
// so replaced with :
var wfApp = new WorkflowApplication(activity, new WorkflowIdentity("v1", new Version(1, 0), string.Empty));
ConfigureWorkflowApplication(wfApp);
var trackerInstance = StateMachineStateTracker.LoadInstance(instanceId, wfApp.WorkflowDefinition, ConnectionString);
if (trackerInstance != null)
{
return trackerInstance.CurrentState;
}
return string.Empty;
}
private void ConfigureWorkflowApplication(WorkflowApplication wfApp)
{
// Configure the persistence store.
wfApp.InstanceStore = Store;
// State machine tracker taken from http://code.msdn.microsoft.com/windowsdesktop/Windows-Workflow-fee72008
var tracker = new StateMachineStateTracker(wfApp.WorkflowDefinition);
wfApp.Extensions.Add(tracker);
wfApp.Extensions.Add(new StateTrackerPersistenceProvider(tracker));
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Debug.WriteLine("Workflow completed.");
};
wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
Debug.WriteLine(string.Format("Workflow Aborted. Exception: {0}\r\n{1}",
e.Reason.GetType().FullName,
e.Reason.Message));
};
wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Debug.WriteLine(string.Format("Unhandled Exception: {0}\r\n{1}",
e.UnhandledException.GetType().FullName,
e.UnhandledException.Message));
return UnhandledExceptionAction.Terminate;
};
wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
{
return PersistableIdleAction.Unload;
};
}
}
Now, what i want is to get the workflow version from the instance id, so in my GetCurrentState i call :
var instance = WorkflowApplication.GetInstance(instanceId, Store);
And load the WorkflowApplication with that
var wfApp = new WorkflowApplication(activity, instance.DefinitionIdentity)
this works fine, but i suspect somehow leaves the host of my workflow loaded, and my instance locked.
So when right after calling GetCurrentState(), the client calls Resume() i get the following exception (at the wfApp.Load(instanceId) line).
InstanceLockedException
The execution of an InstancePersistenceCommand was interrupted because the instance 'a1bcbd11-50fc-4a72-b5d2-87b71d0c3c45' is locked by a different instance owner. This error usually occurs because a different host has the instance loaded. The instance owner ID of the owner or host with a lock on the instance is '190ea0d9-788f-4278-883e-84226f5788bc'.
This exception goes away when i stop using the WorkflowApplication.GetInstance method and specifying the version manually, but i would rather avoid that.
So what am i doing wrong here ?
Googling around took me to these pages :
InstanceLockedException: How to handle locking issues with WF 4.0?
-> Looks like the issue i have, but i am not sure how where i can set this timeToUnload value in my current code as i do not use WorkflowServiceHost. Should i just create a new one in each webmethod ? or make a singleton ?
-> Tried this on my store with a TimeSpan of 1 sec, did not help either.
Any suggestions would be greatly appreciated :-)
FOLLOW UP
After reading this post : Error when attempting to Resume a Windows Workflow
I have created a DisposableStore class designed as follow :
public class DisposableStore : IDisposable
{
private const string ConnectionString = "Server=.\\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI";
private SqlWorkflowInstanceStore _store;
private InstanceHandle _handle;
public SqlWorkflowInstanceStore Store
{
get
{
if (_store == null)
{
Debug.WriteLine("Building new store");
_store = new SqlWorkflowInstanceStore(ConnectionString);
StateMachineStateTracker.Promote(_store);
_handle = _store.CreateInstanceHandle();
InstanceView view = _store.Execute(_handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));
_store.DefaultInstanceOwner = view.InstanceOwner;
}
return _store;
}
}
public void Dispose()
{
var deleteOwnerCmd = new DeleteWorkflowOwnerCommand();
Store.Execute(_handle, deleteOwnerCmd, TimeSpan.FromSeconds(30));
}
}
And i use it as follow :
[WebMethod]
public bool Resume(Guid instanceId, string bookmarkName, object bookmarkParameter)
{
Debug.WriteLine(string.Format("Resume - Service id : {0}", _serviceId));
WorkflowDescriptor descriptor;
using (var store = new DisposableStore())
{
var instance = WorkflowApplication.GetInstance(instanceId, store.Store);
descriptor = WorkflowLocator.GetWorflowFromIdentity(instance.DefinitionIdentity);
}
using (var store = new DisposableStore())
{
var wfApp = new WorkflowApplication(descriptor.Activity, descriptor.Identity);
ConfigureWorkflowApplication(wfApp, store.Store);
wfApp.Load(instanceId);
var sync = new AutoResetEvent(false);
wfApp.Idle = x => sync.Set();
wfApp.Completed = x=> sync.Set();
wfApp.ResumeBookmark(bookmarkName, bookmarkParameter);
sync.WaitOne();
}
return true;
}
Things get better with this approach, meaning i no longer get the InstanceLockedException, but now when the Resume method returns to the client, i my wfApp gets aborted :
Exception: System.Runtime.DurableInstancing.InstanceOwnerException The execution of an InstancePersistenceCommand was interrupted because the instance owner registration for owner ID '33ed6492-0685-4f31-8652-4b91acaf50ef' has become invalid. This error indicates that the in-memory copy of all instances locked by this owner have become stale and should be discarded, along with the InstanceHandles. Typically, this error is best handled by restarting the host