2016-02-05 3 views
5

DearsSqlWorkflowInstanceStore WaitForEvents kehrt HasRunnableWorkflowEvent aber LoadRunnableInstance nicht

Bitte mir helfen, mit verzögerten Wiederherstellen (und blieb) Workflows.

Ich versuche, auf selbst gehosteten Workflow-Speicher zu überprüfen ist es eine Instanz, die sich verzögert und wieder aufgenommen werden kann. Zu Testzwecken habe ich eine Dummy-Aktivität erstellt, die verzögert ist und bei der Verzögerung weiterhin auftritt.

allgemein wieder aufnehmen Prozess wie folgt aussieht:

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 

es gut funktioniert, wenn Speicher created|initialized ist, WaitForEvents genannt wird, zu speichern geschlossen. In einem solchen Fall Speicher liest alle verfügbaren Workflows von beharrte DB und wirft Timeout Ausnahme, wenn es keine workflows verfügbar fortgesetzt werden soll.

Das Problem tritt auf, wenn Speicher erstellt wird und Schleife gestartet wird, nur für WaitForEvents (das gleiche passiert mit BeginWaitForEvents). In diesem Fall liest es alle verfügbaren workflows von DB (mit richtigen IDs), aber dann anstelle von timeout exception wird es eine weitere Instanz lesen (Ich weiß genau, wie viele workflows ist da bereit, wieder aufgenommen werden, da separate Test database verwenden). Aber nicht lesen und throws InstanceNotReadyException. In catch bin ich Überprüfung workflowApplication.Id, aber es war nicht mit meinem Test vor gespeichert.

Ich habe versucht, auf neue (leere) persistente Datenbank und das Ergebnis ist die gleiche :(

Dieser Code nicht ausgeführt werden:

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 

Aber diese wie erwartet funktioniert:

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 

Was für eine beste Lösung in einem solchen Fall ist? für InstanceNotReadyException leer catch hinzufügen und es ignorieren?

Hier sind meine Tests

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; 
} 

Hier wird store Wrapper Definition, die ich für Test bin:

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; 
    } 
} 
+0

upvoted für die grundlegende Frage. Hoffe, du wirst mehr Aufmerksamkeit bekommen. Eigentlich denke ich, es könnte einige Gründe geben, warum deine Frage trotz deiner '+ 150' Kopfgeld weniger Aufmerksamkeit bekommt. (1) Nicht viele Menschen mit 'Workflow', (2) Sie fordern' Beste Solution', die besser geeignet Frage für Code Review sein könnte statt Stack-Überlauf, (3) Dies ist eher gering, aber Ihr Testcode ist ziemlich Lange ohne sinnvolle Erklärung (was in Ordnung ist, wenn Leute Ihr Problem direkt von ihr kopieren können, aber ansonsten das Lesen des Codes abhalten). Hoffe, du bekommst deine Antwort obwohl ... – Ian

+0

Die fehlerhaften und nicht fehlerbehafteten Schleifen machen zwei getrennte Dinge, wobei Sie beim Starten einen neuen StoreWrapper für jede Iteration starten. und die andere verwendet einen StoreWrapper für jede Iteration. Es war ziemlich lange her, dass ich den Workflow verwendet habe, aber wenn ich den Code sehe, denke ich, dass Ihr Store Wrapper kein wiederverwendbares Objekt ist. Sie könnten also in Betracht ziehen, mit der Arbeitsschleife zu gehen. oder verursacht es andere Probleme? – Thorarins

+0

@Thorarins Ich kann Samples mit einfachen SqlWorkflowInstanceStore schreiben, aber das Problem wird bestehen bleiben. Ich habe Beispiele im Web gesehen, die dieselbe SqlWorkflowInstanceStore-Instanz verwenden, um die WF-Datenbank nach betriebsbereiten Workflows abzufragen. Ich kann neue Instanzen innerhalb der Schleife erstellen, aber ich befürchte, dass dies zusätzlichen Speicheraufwand und Zeitverzögerung verursacht. – oleksa

Antwort

0

Ich bin nicht Workflow Foundation Experte so meine Antwort auf die offiziellen Beispiele von Microsoft basiert . Die erste ist WF4 host resumes delayed workflow (CSWF4LongRunningHost) und die zweite ist Microsoft.Samples.AbsoluteDelay. In beiden Proben erhalten Sie einen Code ähnlich wie bei Ihnen finden d.h .:

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

Unter Berücksichtigung dieser Tatsache ist die Antwort, dass Sie mit der rechten und der leere Fang für InstanceNotReadyException sind, ist eine gute Lösung.

Verwandte Themen