2016-02-05 14 views
5

drodzySqlWorkflowInstanceStore WaitForEvents zwraca HasRunnableWorkflowEvent ale LoadRunnableInstance nie

Proszę mi pomóc z przywracania opóźniony (a trwało) przepływy pracy.

Próbuję sprawdzić samobsługujący sklep przepływu pracy, czy wystąpił opóźnienie i można go wznowić. Do celów testowych utworzyłem nieaktywną aktywność manekina, która utrzymuje się z opóźnieniem.

ogólnie wznowić proces wygląda następująco:

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 

to działa dobrze, jeśli sklep jest created|initialized, WaitForEvents nazywa, sklep jest zamknięty. W takim przypadku magazyn odczytuje wszystkie dostępne przepływy pracy z utrwalonego DB i generuje wyjątek limitu czasu, jeśli nie ma możliwości wznowienia pracy z workflows.

Problem występuje, jeśli utworzono magazyn i uruchomiono pętlę tylko dla WaitForEvents (to samo dzieje się z BeginWaitForEvents). W takim przypadku odczytuje on wszystkie dostępne workflows z DB (z odpowiednimi identyfikatorami), ale zamiast timeout exception odczyta jeszcze jedną instancję (dokładnie wiem, ile jest workflows gotowych do wznowienia, ponieważ używa się osobnego testu database). Ale nie można odczytać i throws InstanceNotReadyException. W wersji catch sprawdzam workflowApplication.Id, ale nie został on wcześniej zapisany w moim teście.

Próbowałem uruchomić na nowy (pusty) uporczywego bazy danych i wynik jest taki sam :(

Kod ten nie powiedzie się:

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 

Ale ten działa zgodnie z oczekiwaniami:

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 

Jakie jest najlepsze rozwiązanie w takim przypadku? Dodaj puste catch dla InstanceNotReadyException i zignoruj ​​je?

Oto moje testy

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

Oto definicja sklep wrapper Używam do testu:

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

Wznowienie dla podstawowego pytania. Mam nadzieję, że zyskasz więcej uwagi. Właściwie, wydaje mi się, że może być kilka powodów, dla których twoje pytanie jest mniej uwagi, pomimo twojej nagrody + 150. (1) Niewiele osób korzysta z 'Workflow', (2) Pytasz o' Najlepsze rozwiązanie', które może być bardziej odpowiednim pytaniem do przeglądu kodu niż Stack Overflow, (3) To jest raczej niewielkie, ale twój kod testowy jest ładny długo bez znaczących wyjaśnień (co jest ok, jeśli ludzie mogą naprawdę powielić swój problem bezpośrednio z niego, ale inaczej zniechęci to czytanie kodu). Mam nadzieję, że otrzymasz odpowiedź ... – Ian

+0

Pętle upadające i niezawierające robią dwie osobne rzeczy, a na początku uruchamiasz nową StoreWrapper dla każdej iteracji. a drugi ponownie używa jednej StoreWrapper dla każdej iteracji. To było całkiem dawno temu użyłem przepływu pracy, ale widząc kod, wydaje mi się, że twoje opakowanie sklepu nie jest obiektem wielokrotnego użytku. więc możesz po prostu rozważyć przejście z pętlą roboczą. lub czy powoduje inne problemy? – Thorarins

+0

@ Thorarins Mogę przepisać próbki za pomocą zwykłego SqlWorkflowInstanceStore, ale problem będzie się utrzymywał. Widziałem próbki w Internecie, które używają tej samej instancji SqlWorkflowInstanceStore do wysyłania kwerendy do bazy danych WF dla przepływów pracy gotowych do uruchomienia. Mogę utworzyć nową instancję wewnątrz pętli, ale obawiam się, że spowoduje to dodatkowe obciążenie pamięci i opóźnienie. – oleksa

Odpowiedz

0

Nie jestem ekspertem Workflow Foundation więc moja odpowiedź jest oparta na oficjalnych przykładów z Microsoft . Pierwszy to WF4 host resumes delayed workflow (CSWF4LongRunningHost), a drugi to Microsoft.Samples.AbsoluteDelay. W obu próbkach można znaleźć kodu podobnego do Ciebie t j .:

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

Biorąc to pod uwagę, że odpowiedź jest, że masz rację, a pusty haczyk na InstanceNotReadyException jest dobrym rozwiązaniem.

Powiązane problemy