Součástí WF je knihovna activities, které můžete používat (záměrně se snažím nepočešťovat activity, aby bylo zřejmé, co mám na mysli). Některé se používají triviálně, jiné v sobě skrývají několik záludností. Jednou z těch záludnějších je Replicator Activity.
Tuto activity použijete, pokud budete potřebovat vykonat nějakou činnost nad kolekcí či polem. Něco jako foreach (velice zjednodušeno). Oproti foreach je tu jeden zásadní rozdíl. Akce můžete pustit buď sekvenčně, nebo paralelně.
Pojďme se podívat na tuto activity podrobněji. Po jejím vložení do workflow je situace následující:

Nápisem Drop Activities Here (množné číslo) se nenechte zmást, můžete tam vložit pouze jednu activity. Pokud jich tam potřebujete dát víc, tak je cesta jednoduchá. Nejdřív vložíte Sequence activity nebo jinou kontajnerovou activity a pak už do ní můžete naložit cokoliv potřebujete.
Než se posunu dál, podívejme se na vlastnosti, které Replicator Activity má (seřazeno dle významu):
|
(name) |
Zde je význam doufám jasný |
|
Description |
Fantazii se meze nekladou, co sem zadat |
|
Enabled |
True/False. Pokud je False, activity se jednoduše přeskočí |
|
UntilCondition |
Podmínka, která se bude vyhodnocovat po vykonání child activity. Pokud je výsledek True, dál se nepokračuje. Pozor na okamžik vyhodnocení v závislosti na nastavení vlastnosti ExecutionType. Podmínka může být definovaná buď pomocí metody nebo rule. |
|
ExecutionType |
Sequence (default) / Parallel. Určuje, zda se child aktivity bude vykonávat sekvenčně, nebo zda se spustí paralelně. Paralelně ovšem neznamená ve stejném okamžiku ve více vláknech. Nadále platí single-threaded model WF. |
|
InitialChildData |
Kolekce nebo pole podporující interface IList. Replikator pomocí tohoto interfacu bude postupně vybírat z kolekce nebo pole jednotlivé prvky a předá je do child activity. Mechanizmus předání bude popsán dále. |
|
Handlery |
|
Initialized |
Inicializace samotného replicatoru |
|
Completed |
Konec činnosti replicatoru, všechny vnořené activity jsou hotové |
|
ChildInitialized |
Start nové child activity. Místo vhodné pro nastavení dat určených ze vstupní kolekce pro danou child activity |
|
ChildCompleted |
Jedna z child activit skončila, je na vás, co s tím |
Pro prvotní test jsem nachystal velice jednoduchou konfiguraci. Replicator Activity obsahuje pouze jednu Code Activity, která na obrazovku vypíše slůvko „Akce". Vlastnost InitialChildData je nabindovaná na kolekci List<int> obsahující čísla 1 až 10. Nic víc pro první pokus nastaveno není.
V tomto případě vypadá výpis programu takto:

Nic překvapujícího. Uděláme malou, na první pohled nijak strašnou změnu. Ať víme, že se něco děje s podmínkou UntilCondition, nastavme ji na metodu, které vypíše nějaký text a VŽDY nastaví e.Result na false.
Pokud je nastaveno ExecutionType na sekvenční, bude výsledek běhu tento:

Co z toho výpisu lze vyčíst:
- a) Než se spustí poprvé vnořená activity, provede se test na UntilCondition. Pokud by vrátil true, child activity se nespustí ani jednou
- b) Po ukončení každé child activity se vykoná opět vyhodnocení UntilCondition. Pokud by vrátilo true, další child activity se nespustí.
- c) POZOR! Neproběhl výpis „Konec workflow..." Není to tím, že by se mi to tam nevešlo, ale tím, že replicator vyhodnotil podmínku UntilCondition jako false, snaží se tedy dál vykonávat vnořený kód, tam ale nemá už z listu co poslat a aplikace je tuhá.
Pro odstranění problému c) je nutno zajistit, že v podmínce otestujete i vlastnost AllChildrenComplete a pokud je true, vždy vrátíte true. Upravená metoda pro UntilCondition může vypadat v mém demo případě takto:
private void UntilPodminka(object sender, ConditionalEventArgs e)
{
Console.Write("Vyhodnocuji podminku");
ReplicatorActivity ra = (ReplicatorActivity) sender;
if (ra.AllChildrenComplete)
e.Result = true;
else
e.Result = false;
}
Teď už je výpis kompletní a vypadá takto:

Co se stane, když změním ExecutionType na Parallel? Zde je vidět situace v tomto jednoduchém případě:

Jak je vidět, byl běh následující:
- a) Na začátku opět jako první proběhl test UntilCondition
- b) Pro všechny prvky mé kolekce byla spuštěna child activity. Ještě jednou zdůrazňuji, že i když je ExecutionType Parallel, nejedná se o běh více activities v jednom okamžiku.
- c) Opět po skončení každé child activity byl spuštěn test na UntilCondition. (Pokud bych ho neměl už opravený, i tady by to vytuhlo)
Varianta, kterou jsem testoval, byla jednoduchá, protože vnořená activity byla pouze code activity a nedostala se nikdy do stavu, že by byla možnost naplánovat běh jiné activity. Co ale nastane za situace, že běh child activities bude dlouhý a bude se skládat z několika activities. Pak může nastat situace, že některá z prvních activities co skončí, zajistí, že se UntilCondition vyhodnotí jako true. A co pak s těma, co jsou ještě naplánované, že poběží?
Já jsem mírně upravil můj demonstrační příklad.

Sequence Activity jsem musel přidat, aby replikátor mohl replikovat činnost skládající se z tří částí. Akce activity nadále dělá to, co dělala, vypisuje pouze „Akce". Nová activity Hotovo zase vypíše „Hotovo". Delay activity Cekani se nastavuje v handleru události ChildInitialized. Kod je tento:
private void StartPotomka(object sender, ReplicatorChildEventArgs e)
{
SequenceActivity sa = (SequenceActivity)e.Activity;
DelayActivity da = (DelayActivity)sa.Activities[1];
int sec = 2 + (pocitadlo % 3) * 5;
pocitadlo++;
Console.WriteLine("Nastavuji delay na " + sec.ToString());
da.TimeoutDuration = new TimeSpan(0, 0, sec);
}
Pro jednoduchost jsem vynechal řešení možných chyb castingu. Proměnná pocitadlo je privátní členský prvek třídy ReplicatorDemo, která realizuje workflow. Slouží na to, aby přiřadil prvnímu a čtvrtému replikovanému potomku delay na 2 vteřiny, druhému a pátému na 7 vteřin a třetímu na 12 vteřin.
Další počítadlo jsem přidal pro napočítávání počtu průchodů přes UntilCondition. Je nastaveno tak, aby při třetím a dalších průchodem nastavilo výsledek na true.
Dále jsem pro zkrácení výstupu zmenšil kolekci List<int> tak, že obsahuje jen čísla 1 až 5.
Teď se projeví moje změny:

Co je z uvedeného výsledku vidět:
- a) Před spuštěním první child activity proběhne inicializace všech replikovaných potomků
- b) Pak proběhnou všechny první activity ze sekvence (5x Akce)
- c) Delay se postará o to, že první skončí 1. a 4. replikace (Delay byl jen 2 vteřiny) a při vyhodnocení vrátí UntilCondition true
- d) Všechny neukončené sekvence jsou „odstřeleny" (uvedeny do režimu Cancel) a tím pádem žádná ze zbývajích sekvencí už nenapíše „Hotovo"
Podstatné je si zapamatovat, že jakmile je podmínka UntilConditition true, všechny ještě běžící sekvence se zruší a nedostanou šanci dokončit, co mají rozděláno. Jediná možnost je připravit tzv. Cancel Handler na úrovni sekvenční activity. V něm můžete při předčasném ukončení činnosti provést patřičný úklid. Já tam pro ukázku přidal jen výpis hlášky „Cancel".
Zde je výsledek:

Jak je vidět, v každé ze tří předčasně ukončených sekvencí proběhl Cancel Handler.
Posledním bodem který chci vysvětlit je cesta, jak dostat patřičná data do child activity při jejím spuštění. Řešení vyžaduje trošku víc práce. Jako první krok je nutno vytvořit si vlastní custom activity děděním od např. Sequence Activiy. Jediné, co do té activity přidáte je členský prvek (nejlépe Dependency Property), který bude sloužit k uložení informace specifické pro danou spuštěnou kopii child activity. Pak do Replicator Activity vložíte tuto vlastní activity a připravíte handler pro událost ChildInitialized. V ní pak pomocí vlastnosti InstanceData event argumentu zjistíte, jaká hodnota z kolekce je přiřazená této replikované kopii. Může to vypadat např. takto:
private void StartPotomka(object sender, ReplicatorChildEventArgs e)
{
((MojeActivity)e.Activity).MojeData = (int) e.InstanceData;
}
Po této poslední úpravě mi už prográmek pěkně píše i informaci, která child activity právě běží.

Pokud jste se dočetli až sem, poprosím o zaslání Vašeho názoru, zda má smysl připravit další povídání o konkrétních activities a jejich chování. Můžete mi napsat i jiné tipy na to, co by Vás z oblasti WF zajímalo.