SqlWorkflowInstanceStore WaitForEvents返回HasRunnableWorkflowEvent但LoadRunnableInstance失败

时间:2016-02-05 16:31:55

标签: c# workflow-foundation-4

逗人

请帮我恢复延迟(和持久)的工作流程。

我正在尝试检查自托管工作流程存储是否存在任何延迟且可以恢复的实例。出于测试目的,我创建了延迟的虚拟活动,并且在延迟时间内持续存在。

一般恢复过程如下:

get WF definition
configure sql instance store 
call WaitForEvents
is there event with HasRunnableWorkflowEvent.Value name and if it is
create WorkflowApplication object and execute LoadRunnableInstance  method

如果商店为created|initialized,调用WaitForEvents,商店已关闭,则效果正常。在这种情况下,如果没有workflows可用于恢复,则存储将从持久数据库中读取所有可用工作流并抛出超时异常。

如果创建了存储并且仅为WaitForEvents启动了循环,则会出现问题(BeginWaitForEvents也会发生同样的情况)。在这种情况下,它会从workflows(具有正确的ID)读取所有可用的DB,但之后不再是timeout exception,而是会再读取一个实例(我确切知道有多少workflows是否准备好恢复,因为使用单独的测试database)。但未能阅读和throws InstanceNotReadyException。在catch我正在检查workflowApplication.Id,但之前我的测试没有保存。

我尝试在新的(空)持久数据库上运行,结果是一样的:(

此代码失败:

using (var storeWrapper = new StoreWrapper(wf, connStr))
    for (int q = 0; q < 5; q++)
    {
        var id = Resume(storeWrapper); // InstanceNotReadyException here when all activities is resumed

但是这个按预期工作:

for (int q = 0; q < 5; q++)
    using (var storeWrapper = new StoreWrapper(wf, connStr))
    {
        var id = Resume(storeWrapper); // timeout exception here or beginWaitForEvents continues to wait

在这种情况下,最佳解决方案是什么?为catch添加空InstanceNotReadyException并忽略它?

以下是我的测试

const int delayTime = 15;
string connStr = "Server=db;Database=AppFabricDb_Test;Integrated Security=True;";

[TestMethod]
public void PersistOneOnIdleAndResume()
{
    var wf = GetDelayActivity();

    using (var storeWrapper = new StoreWrapper(wf, connStr))
    {
        var id = CreateAndRun(storeWrapper);
        Trace.WriteLine(string.Format("done {0}", id));
    }

    using (var storeWrapper = new StoreWrapper(wf, connStr))
    for (int q = 0; q < 5; q++)
    {
        var id = Resume(storeWrapper);
        Trace.WriteLine(string.Format("resumed {0}", id));
    }

}

Activity GetDelayActivity(string addName = "")
{
    var name = new Variable<string>(string.Format("incr{0}", addName));
    Activity wf = new Sequence
    {
        DisplayName = "testDelayActivity",
        Variables = { name, new Variable<string>("CustomDataContext") },
        Activities =
            {
            new WriteLine
                {
                    Text = string.Format("before delay {0}", delayTime)
                },
                new Delay
                {
                    Duration = new InArgument<TimeSpan>(new TimeSpan(0, 0, delayTime))
                },
                new WriteLine
                {
                    Text = "after delay"
                }
            }
    };
    return wf;
}

Guid CreateAndRun(StoreWrapper sw)
{
    var idleEvent = new AutoResetEvent(false);
    var wfApp = sw.GetApplication();

    wfApp.Idle = e => idleEvent.Set();
    wfApp.Aborted = e => idleEvent.Set();
    wfApp.Completed = e => idleEvent.Set();

    wfApp.Run();

    idleEvent.WaitOne(40 * 1000);
    var res = wfApp.Id;
    wfApp.Unload();
    return res;
}

Guid Resume(StoreWrapper sw)
{
    var res = Guid.Empty;

    var events = sw.GetStore().WaitForEvents(sw.Handle, new TimeSpan(0, 0, delayTime));

    if (events.Any(e => e.Equals(HasRunnableWorkflowEvent.Value)))
    {
        var idleEvent = new AutoResetEvent(false);

        var obj = sw.GetApplication();
        try
        {
            obj.LoadRunnableInstance(); //instancenotready here if the same store has read all instances from DB and no delayed left

            obj.Idle = e => idleEvent.Set();
            obj.Completed = e => idleEvent.Set();

            obj.Run();

            idleEvent.WaitOne(40 * 1000);

            res = obj.Id;

            obj.Unload();
        }
        catch (InstanceNotReadyException)
        {
            Trace.TraceError("failed to resume {0} {1} {2}", obj.Id
                , obj.DefinitionIdentity == null ? null : obj.DefinitionIdentity.Name
                , obj.DefinitionIdentity == null ? null : obj.DefinitionIdentity.Version);
            foreach (var e in events)
            {
                Trace.TraceWarning("event {0}", e.Name);
            }
            throw;
        }
    }
    return res;
}

这是我用于测试的商店包装器定义:

public class StoreWrapper : IDisposable
{
    Activity WfDefinition { get; set; }

    public static readonly XName WorkflowHostTypePropertyName = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties").GetName("WorkflowHostType");
    public StoreWrapper(Activity wfDefinition, string connectionStr)
    {
        _store = new SqlWorkflowInstanceStore(connectionStr);

        HostTypeName = XName.Get(wfDefinition.DisplayName, "ttt.workflow");

        WfDefinition = wfDefinition;

    }

    SqlWorkflowInstanceStore _store;

    public SqlWorkflowInstanceStore GetStore()
    {
        if (Handle == null)
        {

            InitStore(_store, WfDefinition);
            Handle = _store.CreateInstanceHandle();

            var view = _store.Execute(Handle, new CreateWorkflowOwnerCommand
            {
                InstanceOwnerMetadata = { { WorkflowHostTypePropertyName, new InstanceValue(HostTypeName) } }
            }, TimeSpan.FromSeconds(30));

            _store.DefaultInstanceOwner = view.InstanceOwner;

            //Trace.WriteLine(string.Format("{0} owns {1}", view.InstanceOwner.InstanceOwnerId, HostTypeName));
        }

        return _store;
    }

    protected virtual void InitStore(SqlWorkflowInstanceStore store, Activity wfDefinition)
    {
    }

    public InstanceHandle Handle { get; protected set; }

    XName HostTypeName { get; set; }

    public void Dispose()
    {
        if (Handle != null)
        {
            var deleteOwner = new DeleteWorkflowOwnerCommand();

            //Trace.WriteLine(string.Format("{0} frees {1}", Store.DefaultInstanceOwner.InstanceOwnerId, HostTypeName));

            _store.Execute(Handle, deleteOwner, TimeSpan.FromSeconds(30));
            Handle.Free();
            Handle = null;

            _store = null;
        }
    }

    public WorkflowApplication GetApplication()
    {
        var wfApp = new WorkflowApplication(WfDefinition);
        wfApp.InstanceStore = GetStore();
        wfApp.PersistableIdle = e => PersistableIdleAction.Persist;

        Dictionary<XName, object> wfScope = new Dictionary<XName, object> { { WorkflowHostTypePropertyName, HostTypeName } };
        wfApp.AddInitialInstanceValues(wfScope);

        return wfApp;
    }
}

1 个答案:

答案 0 :(得分:0)

我不是工作流基础专家,所以我的回答是基于微软的官方示例。第一个是WF4 host resumes delayed workflow (CSWF4LongRunningHost),第二个是Microsoft.Samples.AbsoluteDelay。在这两个样本中,您将找到与您的代码类似的代码,例如:

try 
{
    wfApp.LoadRunnableInstance();
    ...
} 
catch (InstanceNotReadyException) 
{
    //Some logging
}

考虑到这一点,答案是你是对的,InstanceNotReadyException的空洞是一个很好的解决方案。