2016-02-05 5 views
5

DearsSqlWorkflowInstanceStore WaitForEvents ritorna HasRunnableWorkflowEvent ma non riesce LoadRunnableInstance

Please help me con il ripristino ritardato (e persistito) i flussi di lavoro.

Sto provando a controllare sull'archivio del flusso di lavoro auto-ospitato c'è qualche istanza che è stata ritardata e può essere ripresa. A scopo di test, ho creato attività fittizie che sono in ritardo e persistono in ritardo.

generalmente riprendere processo assomiglia:

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 

funziona bene se negozio è created|initialized, WaitForEvents si chiama, negozio è chiuso. In tal caso, l'archivio legge tutti i flussi di lavoro disponibili dal DB persistente e genera un'eccezione di timeout se non è disponibile lo stato workflows da riprendere.

Il problema si verifica se il negozio viene creato e il ciclo viene avviato solo per WaitForEvents (la stessa cosa accade con BeginWaitForEvents). In tal caso, viene letto tutto il workflows disponibile da DB (con ID corretto), ma al posto di timeout exception leggerà un'altra istanza (so esattamente quanti workflows sono pronti per essere ripresi perché utilizzando il test separato database). Ma non riesce a leggere e throws InstanceNotReadyException. Nel catch sto controllando workflowApplication.Id, ma non è stato salvato con il mio test prima.

Ho cercato di girare su nuove (vuoto) del database persistente e risultato è lo stesso :(

Questo codice non riesce:

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 

Ma questo funziona come previsto:

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 

Qual è la soluzione migliore in tal caso? Aggiungere vuoto catch per InstanceNotReadyException e ignorarlo?

Qui ci sono i miei test

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

Qui è la definizione negozio involucro che sto utilizzando per il test:

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

In corso di aggiornamento per la domanda di base. Spero che avrai più attenzione. In realtà, penso, potrebbero esserci un paio di motivi per cui la tua domanda riceve meno attenzione nonostante la taglia '+ 150'. (1) Non molte persone usano 'Workflow', (2) Stai chiedendo' Best Solution' che potrebbe essere la domanda più adatta per Code Review piuttosto che Stack Overflow, (3) Questo è piuttosto secondario, ma il tuo codice di test è carino lungo senza una spiegazione significativa (che è ok se la gente può davvero duplicare il problema direttamente da esso, ma altrimenti scoraggerà la lettura del codice). Spero che tu ottenga la tua risposta però ... – Ian

+0

i loop non riusciti e non falliti stanno facendo due cose separate, il wokring si avvia un nuovo StoreWrapper per ogni iterazione. e l'altro riutilizzate StoreWrapper per ogni iterazione. è stato qualche tempo fa che ho usato il flusso di lavoro, ma vedere il codice mi fa pensare che il tuo wrapper del negozio non sia un oggetto riutilizzabile. quindi potresti semplicemente considerare di andare con il ciclo di lavoro. o causa altri problemi? – Thorarins

+0

@Thorarins Posso riscrivere gli esempi utilizzando SqlWorkflowInstanceStore semplice ma il problema persisterà. Ho visto esempi nel web che utilizza la stessa istanza SqlWorkflowInstanceStore per interrogare il database WF per i flussi di lavoro pronti per l'esecuzione. Posso creare nuove istanze all'interno del ciclo, ma temo che ciò causerà sovraccarico di memoria e ritardo. – oleksa

risposta

0

Io non sono esperto di workflow fondazione quindi la mia risposta è basata su esempi ufficiali da Microsoft . Il primo è WF4 host resumes delayed workflow (CSWF4LongRunningHost) e il secondo è Microsoft.Samples.AbsoluteDelay. In entrambi i campioni, troverete un codice simile al tuo cioè .:

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

Tenendo conto di ciò, la risposta è che lei ha ragione e il pescato vuoto per InstanceNotReadyException è una buona soluzione.

Problemi correlati