Daily Archives: 2007.10.23. 1:58

WorkflowInstanceId kötése

Aki csinált már olyan workflowt, amelynek kommunikálnia kellett a külvilággal, annak lehet, hogy szüksége volt arra, hogy a workflow egyedi azonosítóját átadja egy activitynek vagy egy külső komponensnek. Az "átadás" nem jelenthet gondot, hiszen a Workflow Designer okos jószág, lehet benne tulajdonságokat kötögetni.

Sajnálatos módon azonban a WorkflowInstanceId tulajdonságot nem kínálja fel, hiába keresünk System.Guid típushoz párt. Mit tehet ilyenkor az egyszeri workflow programozó: definiál egy tulajdonságot, ami majd becsomagolja ezt a hívást, a tulajdonságot pedig public láthatóságra állítja, hogy köthető legyen:

    public Guid CurrentInstanceId
    {
        get
        {
            return this.WorkflowInstanceId;
        }
    }

Ettől kezdve azonban furcsa jelenségek ütik fel a fejüket, mintha minden a feje tetejére állt volna, például:

  • A workflow példány tökéletesen lefut, de az utolsó activity utáni pillanatban mégis elszáll, pedig oda nem is írtunk kódot.
  • A workflow példány az élete végén 100%-ra terheli a processzort.
  • Elszáll az alkalmazásunk, mert eddigi tökéletesen működő helyen azt a hibaüzenetet kapjuk, hogy az adott művelet csak workflow runtime threaden hajtható végre.
  • A Visual Studio debug módban nem képes megmutatni a workflowt, mert szerinte valami érvénytelen művelettel próbálkozunk, amit csak futási időben lehet elvégezni.

Mindennek az oka pedig ez a kis ártatlannak tűnő tulajdonság. De mi lehet a háttérben? Gondoljunk arra, hogy a this.WorkflowInstanceId tulajdonság kiolvasásának akkor van értelme, ha a workflow példány fut és a workflow runtime tud róla. Vannak azonban olyan pillanatok, amik nem esnek ebbe a csoportba, a Workflow Foundation mégis megpróbálja kiolvasni a workflow példány tulajdonságait. Ez történik például akkor, amikor a WorkflowRuntime.WorkflowCompleted eseménykezelőnek megpróbál átadni egy WorkflowCompletedEventArgs paramétert. Ebben az e paraméterben ugyanis van egy OutputParameters tulajdonság, ami egy szótár típusú gyűjteményen keresztül teszi elérhetővé a workflow példány tulajdonságait. Amikor a rendszer megpróbálja felépíteni ezt a dictionary-t, akkor bizony a workflow példányunk már éppen nem él.

Mit tehetünk akkor, ha mindenáron kötni szeretnénk a folyamat egyedi azonosítóját? Készíthetünk egy publikus property-t, ami nem közvetlenül a this.WorkflowInstanceId tulajdonságot olvassa ki, hanem egy olyan változó tartalmát, ahova korábban már átmásoltuk ezt az értéket. Ha biztosak vagyunk abban, hogy ezt a tulajdonságot a workflow példány futása közben valamelyik activity kiolvassa, akkor írhatjuk például ezt:

    private Guid _instanceId;

    public Guid CurrentInstanceId
    {
        get
        {
            if( this._instanceId.Equals( Guid.Empty ) )
            {
                this._instanceId = this.WorkflowInstanceId;
            }
            return this._instanceId;
        }
    }

Általában érdemes odafigyelni a tervezésnél, hogy egy döglött workflow példányról már csak a tracking service segítségével szerezhetünk információkat.

 

Technorati tags: , ,
Advertisements

Elbénázott workflow verzió frissítés – Nézz és láss!

A minap hozzá kellett nyúlnom egy korábbi Workflow Foundationös projekthez, aminek a verziószámát szépen meg is növeltem a módosítás után. A biztonság kedvéért nyomtam a Studioban egy Rebuild Solutiont, mégis az alkalmazás első futtatásakor ezt a hibaüzenetet kaptam:

Could not load file or assembly ‘Sample.Workflows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

A fene, valami nem jól frissült. Nosza, újrafordítottam a solution minden egyes projektjét. Semmi javulás. Letöröltem majd újra hozzáadtam a szerelvény referenciákat, a hibaüzenet változatlan. Letöröltem a bin és obj mappákat. Ugyanaz a szöveg. Kibányásztam a Windows mappából a Temporary ASP.NET Files mappát és abból is kitakarítottam mindent, de ez sem segített.

Ekkor kezdtem el gyanakodni, hogy rossz helyen keresem a hibát és ekkor ugrott be a hibakeresés első számú alapszabálya: olvasd el a hibaüzenetet, az egészet, elejétől a végéig!

Nézzük meg, melyik sornál keletkezik a hiba! Ez volt a bűnös:

StateMachineWorkflowInstance instance = new StateMachineWorkflowInstance( runtime, workflowInstanceId );

Ezen nincs mit hibáztatni, ennek tényleg kell a workflow típus a szerelvényből. Olvassuk csak végig a hibaüzenetet, ha már rászántuk magunkat. Na de mi van egy .NET-es hibaüzenet végén? A stack trace a maga hosszú és unalmas formájában, jelen esetben ez:

[FileLoadException: Could not load file or assembly ‘Sample.Workflows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)]
   System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +0
   System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +211
   System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +141
   System.Reflection.Assembly.Load(String assemblyString) +25
   System.UnitySerializationHolder.GetRealObject(StreamingContext context) +355
   System.Runtime.Serialization.ObjectManager.ResolveObjectReference(ObjectHolder holder) +61
   System.Runtime.Serialization.ObjectManager.DoFixups() +2599325
   System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) +203
   System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) +190
   System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream) +12
   System.Workflow.ComponentModel.Activity.Load(Stream stream, Activity outerActivity, IFormatter formatter) +219
   System.Workflow.ComponentModel.Activity.Load(Stream stream, Activity outerActivity) +52
   System.Workflow.Runtime.Hosting.WorkflowPersistenceService.RestoreFromDefaultSerializedForm(Byte[] activityBytes, Activity outerActivity) +114
   System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService.LoadWorkflowInstanceState(Guid id) +249
   System.Workflow.Runtime.WorkflowRuntime.InitializeExecutor(Guid instanceId, CreationContext context, WorkflowExecutor executor, WorkflowInstance workflowInstance) +607
   System.Workflow.Runtime.WorkflowRuntime.Load(Guid key, CreationContext context, WorkflowInstance workflowInstance) +268
   System.Workflow.Runtime.WorkflowRuntime.GetWorkflow(Guid instanceId) +148
   System.Workflow.Activities.StateMachineWorkflowInstance..ctor(WorkflowRuntime runtime, Guid instanceId) +126
   WorkflowManager.GetCurrentState(Guid workflowInstanceId) in c:Documents and SettingsdemoDesktopAspNetWFDemoSolutionWebApp_CodeWorkflowManager.cs:187
   Admin_Default.GetState(Object comment) in c:Documents and SettingsdemoDesktopAspNetWFDemoSolutionWebAdminDefault.aspx.cs:46
   ASP.admin_default_aspx.__DataBinding__control18(Object sender, EventArgs e) in c:Documents and SettingsdemoDesktopAspNetWFDemoSolutionWebAdminDefault.aspx:41
   System.Web.UI.Control.OnDataBinding(EventArgs e) +99
   System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +206
   System.Web.UI.Control.DataBind() +12
   System.Web.UI.Control.DataBindChildren() +216
   System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +216
   System.Web.UI.Control.DataBind() +12
   System.Web.UI.Control.DataBindChildren() +216
   System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +216
   System.Web.UI.Control.DataBind() +12
   System.Web.UI.WebControls.GridView.CreateRow(Int32 rowIndex, Int32 dataSourceIndex, DataControlRowType rowType, DataControlRowState rowState, Boolean dataBind, Object dataItem, DataControlField[] fields, TableRowCollection rows, PagedDataSource pagedDataSource) +221
   System.Web.UI.WebControls.GridView.CreateChildControls(IEnumerable dataSource, Boolean dataBinding) +3004
   System.Web.UI.WebControls.CompositeDataBoundControl.PerformDataBinding(IEnumerable data) +59
   System.Web.UI.WebControls.GridView.PerformDataBinding(IEnumerable data) +11
   System.Web.UI.WebControls.DataBoundControl.OnDataSourceViewSelectCallback(IEnumerable data) +111
   System.Web.UI.DataSourceView.Select(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback) +29
   System.Web.UI.WebControls.DataBoundControl.PerformSelect() +149
   System.Web.UI.WebControls.BaseDataBoundControl.DataBind() +70
   System.Web.UI.WebControls.GridView.DataBind() +4
   System.Web.UI.WebControls.BaseDataBoundControl.EnsureDataBound() +82
   System.Web.UI.WebControls.CompositeDataBoundControl.CreateChildControls() +69
   System.Web.UI.Control.EnsureChildControls() +87
   System.Web.UI.Control.PreRenderRecursiveInternal() +41
   System.Web.UI.Control.PreRenderRecursiveInternal() +161
   System.Web.UI.Control.PreRenderRecursiveInternal() +161
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1360

Ez a hívási lánc önmagában is megérne egy misét és ezt látva talán nem meglepő, hogy a fejlesztők többségéhez hasonlóan én is csak az elejét és a végét néztem meg, abból pedig nem derült ki semmi. 😦

A bőnös pedig ott van a stack trace kellős közepén: WorkflowPersistenceService.RestoreFromDefaultSerializedForm. Ahhoz, hogy egy StateMachineWorkflowInstance példányt kapjunk, a runtime-nak szüksége van a workflow példányra (lásd GetWorkflow fent), amihez természetesen be kell tölteni azt a memóriába (Load), ami jelen esetben azt jelentette, hogy a persistence store-ból vissza kellett állítani a workflow példány állapotát (LoadWorkflowInstanceState). Csakhogy a persistence service sorosításkor eltárolja a workflowt tároló szerelvény teljes nevét, így a verziószámát is, és a visszatöltéskor az alapján próbálja visszaállítani az objektumot sorosított formából.

Tehát ha megváltoztatunk egy workflow definíciót (osztályt), akkor mindig gondoljunk arra, hogy az alapján a definíció alapján már futhatnak workflow példányok, amelyek sorosítva szunnyadnak valamilyen persistence vagy tracking adatbázisban. A friss szerelvényt gond nélkül fogjuk tudni telepíteni és új workflow példányokat is simán fogunk tudni indítani. A hiba csak akkor fog előállni, amikor egy csontváz kiesik a szekrényből: feléled egy várakozó workflow.