A Workflow Foundation runtime alapértelmezés szerint aszinkron módon futtatja az egyes workflow példányokat, azaz minden egyes új példány létrehozásakor kér egy szabad szálat a CLR ThreadPooltól és azon indítja el a workflow-t alkotó activity-k futtatását.
A november végi MSDN konferencián röviden volt szó arról, hogy ez az aszinkron viselkedés néha gondot jelent, például webes környezetben. Az ASP.NET ugyanis maga gondoskodik az egyes kérésekhez érkező szálak kezeléséről és az újabb szálak indítása csak rontana a helyzeten. Arról nem is beszélve, hogy az aszinron végrehajtást nehéz egy oldal rendereléséhez igazítani.
A megoldás az ütemezésért felelős ManualWorkflowSchedulerService hozzáadása a runtime-hoz:
WorkflowRuntime runtime = new WorkflowRuntime();
ManualWorkflowSchedulerService scheduler = new ManualWorkflowSchedulerService();
runtime.AddService( scheduler );
Na ennyivel még nem vagyunk készen. Mivel a ManualWorkflowSchedulerService nem a ThreadPooltól kér szálat, ezért nekünk kell adnunk egyet, amin szinkron módon tudja futtatni a workflow-t, például valahogy így:
WorkflowInstance instance = runtime.CreateWorkflow( typeof( MyWorkflow ) );
instance.Start();
scheduler.RunWorkflow( instance.InstanceId );
A RunWorkflow metódusról ezt írja az MSDN:
This is a synchronous call that uses the current thread to run the workflow. It does not return until the workflow idles, suspends, completes, terminates, or aborts.
Magyarul a RunWorkflow() belerúg egyet a folyamatba és akkor tér vissza, amikor az megáll (befejeződik, várakozik, hibázik stb.). Ez sok esetben nem is rossz, de képzeljük el a következő szituációt: timeout-tal szeretnénk várakozni egy eseményre. Szépen behajítjuk a folyamatba a HandleExternalEvent és a Delay activity-ket, gyönyörűen párhuzamosítva egymással egy Listen activity-n belül. Meghívjuk a RunWorkflow()-t, ami futtatja a folyamatot egészen az eseményre várakozásig, ahogy az a nagy könyvben meg van írva. Ámde a várva várt esemény nem jön. Ekkor kellene kattannia a timeout-ot jelző Delay activity-nek.
Csakhogy nincs hol kattanjon. Nincs min kattanjon. Nincs mikor kattanjon. A workflow példányunk ugyanis egész egyszerűen nem fut. Executing állapotban van, de valójában nem fut. Egy szinkron módon futtatott workflow példány csak akkor fut, ha adunk szálat alá.
Ugyanez a probléma akkor is, ha eseménnyel üzenünk a workflow példánynak. Az esemény szépen beáll a "workflow számára továbbítandó események sorába", de majd csak akkor fog lefutni a workflow definícióban hozzá tartozó eseménykezelő, ha meghívjuk a RunWorkflow() metódust. Nem elég, hogy macerás a kommunikáció, még a folyamat továbbléptetéséről is nekünk kell gondoskodni!
Képzeljük el mindezt egy webalkalmazásban! Ha csak nem hívogatjuk passzióból a RunWorkflow() metódust időnként, nem fognak továbbfutni a workflow példányaink. Márpedig egy webalkalmazásban elég körülményes bármit is megcsinálni "időnként".