Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Windows Workflow Foundation (WF) fornisce diversi metodi di hosting dei flussi di lavoro. WorkflowInvoker offre un modo semplice per richiamare un flusso di lavoro come se fosse una chiamata al metodo e può essere usato solo per i flussi di lavoro che non usano la persistenza. WorkflowApplication offre un modello più completo per l'esecuzione di flussi di lavoro che includono la notifica degli eventi del ciclo di vita, il controllo dell'esecuzione, la ripresa dei segnalibri e la persistenza. WorkflowServiceHost fornisce supporto per le attività di messaggistica e viene usato principalmente con i servizi flusso di lavoro. Questo argomento ti introduce all'hosting del flusso di lavoro con WorkflowInvoker e WorkflowApplication. Per altre informazioni sull'hosting di flussi di lavoro con WorkflowServiceHost, vedere Servizi Flusso di Lavoro e Panoramica di Hosting Servizi Flusso di Lavoro.
Uso di WorkflowInvoker
WorkflowInvoker fornisce un modello per l'esecuzione di un flusso di lavoro come se fosse una chiamata al metodo. Per richiamare un flusso di lavoro usando WorkflowInvoker, chiamare il metodo Invoke e passare la definizione del flusso di lavoro del flusso di lavoro da richiamare. In questo esempio viene richiamata un'attività WriteLine usando WorkflowInvoker.
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
Quando un flusso di lavoro viene richiamato usando WorkflowInvoker, il flusso di lavoro viene eseguito sul thread chiamante e il metodo Invoke blocca fino al completamento del flusso di lavoro, incluso qualsiasi tempo di inattività. Per configurare un intervallo di timeout in cui il flusso di lavoro deve essere completato, usare uno degli overload Invoke che accetta un parametro TimeSpan. In questo esempio un flusso di lavoro viene richiamato due volte con due intervalli di timeout diversi. Il primo flusso di lavoro viene completato, ma il secondo non lo è.
Activity wf = new Sequence()
{
Activities =
{
new WriteLine()
{
Text = "Before the 1 minute delay."
},
new Delay()
{
Duration = TimeSpan.FromMinutes(1)
},
new WriteLine()
{
Text = "After the 1 minute delay."
}
}
};
// This workflow completes successfully.
WorkflowInvoker.Invoke(wf, TimeSpan.FromMinutes(2));
// This workflow does not complete and a TimeoutException
// is thrown.
try
{
WorkflowInvoker.Invoke(wf, TimeSpan.FromSeconds(30));
}
catch (TimeoutException ex)
{
Console.WriteLine(ex.Message);
}
Nota
TimeoutException viene generato solo se l'intervallo di tempo di attesa scade e il flusso di lavoro diventa inattivo durante l'esecuzione. Un flusso di lavoro che richiede più tempo rispetto all'intervallo di timeout specificato viene completato correttamente se il flusso di lavoro non diventa inattivo.
WorkflowInvoker fornisce anche versioni asincrone del metodo invoke. Per altre informazioni, vedere InvokeAsync e BeginInvoke.
Impostazione degli argomenti di input di un flusso di lavoro
I dati possono essere passati a un flusso di lavoro usando un dizionario di parametri di input, dove la chiave è il nome dell'argomento, che vengono mappati agli argomenti di input del flusso di lavoro. In questo esempio viene richiamato un WriteLine e il valore per il relativo argomento Text viene specificato usando il dizionario dei parametri di input.
Activity wf = new WriteLine();
Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World.");
WorkflowInvoker.Invoke(wf, inputs);
Recuperare i parametri di output di un flusso di lavoro
I parametri di output di un flusso di lavoro si possono ottenere usando il dizionario degli output restituito dalla chiamata a Invoke. Nell'esempio seguente viene richiamato un flusso di lavoro costituito da una singola attività Divide con due argomenti di input e due argomenti di output. Quando viene richiamato il flusso di lavoro, viene passato il dizionario arguments che contiene i valori per ogni argomento di input, con chiave in base al nome dell'argomento. Quando viene restituita la chiamata a Invoke, ogni argomento di output viene restituito nel dizionario outputs, anche con chiave in base al nome dell'argomento.
public sealed class Divide : CodeActivity
{
[RequiredArgument]
public InArgument<int> Dividend { get; set; }
[RequiredArgument]
public InArgument<int> Divisor { get; set; }
public OutArgument<int> Remainder { get; set; }
public OutArgument<int> Result { get; set; }
protected override void Execute(CodeActivityContext context)
{
int quotient = Dividend.Get(context) / Divisor.Get(context);
int remainder = Dividend.Get(context) % Divisor.Get(context);
Result.Set(context, quotient);
Remainder.Set(context, remainder);
}
}
int dividend = 500;
int divisor = 36;
Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);
IDictionary<string, object> outputs =
WorkflowInvoker.Invoke(new Divide(), arguments);
Console.WriteLine($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");
Se il flusso di lavoro deriva da ActivityWithResult, ad esempio CodeActivity<TResult> o Activity<TResult>e sono presenti argomenti di output oltre all'argomento di output Result ben definito, è necessario utilizzare un overload non generico di Invoke per recuperare gli argomenti aggiuntivi. A tale scopo, la definizione del flusso di lavoro passata in Invoke deve essere di tipo Activity. In questo esempio l'attività Divide deriva da CodeActivity<int>, ma viene dichiarata come Activity in modo che venga usato un overload non generico di Invoke che restituisce un dizionario di argomenti anziché un singolo valore restituito.
public sealed class Divide : CodeActivity<int>
{
public InArgument<int> Dividend { get; set; }
public InArgument<int> Divisor { get; set; }
public OutArgument<int> Remainder { get; set; }
protected override int Execute(CodeActivityContext context)
{
int quotient = Dividend.Get(context) / Divisor.Get(context);
int remainder = Dividend.Get(context) % Divisor.Get(context);
Remainder.Set(context, remainder);
return quotient;
}
}
int dividend = 500;
int divisor = 36;
Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);
Activity wf = new Divide();
IDictionary<string, object> outputs =
WorkflowInvoker.Invoke(wf, arguments);
Console.WriteLine($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");
Uso di WorkflowApplication
WorkflowApplication offre un set completo di funzionalità per la gestione delle istanze del flusso di lavoro. WorkflowApplication funge da proxy thread-safe per l'effettivo WorkflowInstance, che incapsula il runtime e fornisce metodi per la creazione e il caricamento di istanze del flusso di lavoro, sospensione e ripresa, terminazione e notifica degli eventi del ciclo di vita. Per eseguire un flusso di lavoro usando WorkflowApplication si crea il WorkflowApplication, si sottoscrivono gli eventi del ciclo di vita desiderati, si avvia il flusso di lavoro e si attende il completamento. In questo esempio viene creata una definizione del flusso di lavoro costituita da un'attività WriteLine e viene creata una WorkflowApplication usando la definizione del flusso di lavoro specificata. Completed viene gestito in modo che l'host venga informato al termine del flusso di lavoro, il flusso di lavoro viene avviato con una chiamata a Rune quindi l'host attende il completamento del flusso di lavoro. Al termine del flusso di lavoro, il AutoResetEvent viene impostato e l'applicazione host può riprendere l'esecuzione, come illustrato nell'esempio seguente.
AutoResetEvent syncEvent = new AutoResetEvent(false);
Activity wf = new WriteLine
{
Text = "Hello World."
};
// Create the WorkflowApplication using the desired
// workflow definition.
WorkflowApplication wfApp = new WorkflowApplication(wf);
// Handle the desired lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
syncEvent.Set();
};
// Start the workflow.
wfApp.Run();
// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();
Eventi del ciclo di vita di WorkflowApplication
Oltre a Completed, gli autori host possono ricevere una notifica quando un flusso di lavoro viene scaricato (Unloaded), interrotto (Aborted), diventa inattiva (Idle e PersistableIdle) o si verifica un'eccezione non gestita (OnUnhandledException). Gli sviluppatori di applicazioni flusso di lavoro possono gestire queste notifiche ed eseguire azioni appropriate, come illustrato nell'esempio seguente.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
if (e.CompletionState == ActivityInstanceState.Faulted)
{
Console.WriteLine($"Workflow {e.InstanceId} Terminated.");
Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
}
else if (e.CompletionState == ActivityInstanceState.Canceled)
{
Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
}
else
{
Console.WriteLine($"Workflow {e.InstanceId} Completed.");
// Outputs can be retrieved from the Outputs dictionary,
// keyed by argument name.
// Console.WriteLine($"The winner is {e.Outputs["Winner"]}.");
}
};
wfApp.Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
{
// Display the exception that caused the workflow
// to abort.
Console.WriteLine($"Workflow {e.InstanceId} Aborted.");
Console.WriteLine($"Exception: {e.Reason.GetType().FullName}\n{e.Reason.Message}");
};
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
// Perform any processing that should occur
// when a workflow goes idle. If the workflow can persist,
// both Idle and PersistableIdle are called in that order.
Console.WriteLine($"Workflow {e.InstanceId} Idle.");
};
wfApp.PersistableIdle = delegate (WorkflowApplicationIdleEventArgs e)
{
// Instruct the runtime to persist and unload the workflow.
// Choices are None, Persist, and Unload.
return PersistableIdleAction.Unload;
};
wfApp.Unloaded = delegate (WorkflowApplicationEventArgs e)
{
Console.WriteLine($"Workflow {e.InstanceId} Unloaded.");
};
wfApp.OnUnhandledException = delegate (WorkflowApplicationUnhandledExceptionEventArgs e)
{
// Display the unhandled exception.
Console.WriteLine($"OnUnhandledException in Workflow {e.InstanceId}\n{e.UnhandledException.Message}");
Console.WriteLine($"ExceptionSource: {e.ExceptionSource.DisplayName} - {e.ExceptionSourceInstanceId}");
// Instruct the runtime to terminate the workflow.
// Other choices are Abort and Cancel. Terminate
// is the default if no OnUnhandledException handler
// is present.
return UnhandledExceptionAction.Terminate;
};
Impostazione degli argomenti di input di un flusso di lavoro
I dati possono essere passati a un flusso di lavoro durante l'avvio usando un dizionario di parametri, in modo analogo al modo in cui i dati vengono passati quando si usa WorkflowInvoker. Ogni elemento nel dizionario corrisponde a un argomento di input del flusso di lavoro specificato. In questo esempio viene richiamato un flusso di lavoro costituito da un'attività WriteLine e il relativo argomento Text viene specificato usando il dizionario dei parametri di input.
AutoResetEvent syncEvent = new AutoResetEvent(false);
Activity wf = new WriteLine();
// Create the dictionary of input parameters.
Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World!");
// Create the WorkflowApplication using the desired
// workflow definition and dictionary of input parameters.
WorkflowApplication wfApp = new WorkflowApplication(wf, inputs);
// Handle the desired lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
syncEvent.Set();
};
// Start the workflow.
wfApp.Run();
// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();
Recuperare i parametri di output di un flusso di lavoro
Al termine di un flusso di lavoro, tutti gli argomenti di output possono essere recuperati nel gestore Completed accedendo al dizionario WorkflowApplicationCompletedEventArgs.Outputs. Nell'esempio seguente viene ospitato un flusso di lavoro usando WorkflowApplication. Un'istanza di WorkflowApplication viene costruita usando una definizione del flusso di lavoro costituita da una singola attività DiceRoll. L'attività DiceRoll ha due argomenti di output che rappresentano i risultati dell'operazione di lancio dei dadi. Al termine del flusso di lavoro, gli output vengono recuperati nel gestore Completed.
public sealed class DiceRoll : CodeActivity
{
public OutArgument<int> D1 { get; set; }
public OutArgument<int> D2 { get; set; }
static Random r = new Random();
protected override void Execute(CodeActivityContext context)
{
D1.Set(context, r.Next(1, 7));
D2.Set(context, r.Next(1, 7));
}
}
// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(new DiceRoll());
// Subscribe to any desired workflow lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
if (e.CompletionState == ActivityInstanceState.Faulted)
{
Console.WriteLine($"Workflow {e.InstanceId} Terminated.");
Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
}
else if (e.CompletionState == ActivityInstanceState.Canceled)
{
Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
}
else
{
Console.WriteLine($"Workflow {e.InstanceId} Completed.");
// Outputs can be retrieved from the Outputs dictionary,
// keyed by argument name.
Console.WriteLine($"The two dice are {e.Outputs["D1"]} and {e.Outputs["D2"]}.");
}
};
// Run the workflow.
wfApp.Run();
Nota
WorkflowApplication e WorkflowInvoker accettano un dizionario di argomenti di input e restituiscono un dizionario di argomenti out. Questi parametri, proprietà e valori restituiti del dizionario sono di tipo IDictionary<string, object>. L'effettiva istanza della classe dizionario che viene passata può essere qualsiasi classe che implementa IDictionary<string, object>. In questi esempi viene usato Dictionary<string, object>. Per altre informazioni sui dizionari, vedere IDictionary<TKey,TValue> e Dictionary<TKey,TValue>.
Passaggio di dati in un flusso di lavoro in esecuzione tramite segnalibri
I segnalibri sono il meccanismo in base al quale un'attività può attendere passivamente di essere ripresa e rappresenta un meccanismo per il passaggio di dati in un'istanza del flusso di lavoro in esecuzione. Se un'attività è in attesa di dati, può creare un Bookmark e registrare un metodo di callback da chiamare quando il Bookmark viene ripreso, come illustrato nell'esempio seguente.
public sealed class ReadLine : NativeActivity<string>
{
[RequiredArgument]
public InArgument<string> BookmarkName { get; set; }
protected override void Execute(NativeActivityContext context)
{
// Create a Bookmark and wait for it to be resumed.
context.CreateBookmark(BookmarkName.Get(context),
new BookmarkCallback(OnResumeBookmark));
}
// NativeActivity derived activities that do asynchronous operations by calling
// one of the CreateBookmark overloads defined on System.Activities.NativeActivityContext
// must override the CanInduceIdle property and return true.
protected override bool CanInduceIdle
{
get { return true; }
}
public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
{
// When the Bookmark is resumed, assign its value to
// the Result argument.
Result.Set(context, (string)obj);
}
Quando viene eseguita, l'attività ReadLine crea un Bookmark, registra un callback e quindi attende che il Bookmark venga ripreso. Quando viene ripresa, l'attività ReadLine assegna i dati passati con l'Bookmark al relativo argomento Result. In questo esempio viene creato un flusso di lavoro che usa l'attività ReadLine per raccogliere il nome dell'utente e visualizzarlo nella finestra della console.
Variable<string> name = new Variable<string>();
Activity wf = new Sequence
{
Variables = { name },
Activities =
{
new WriteLine
{
Text = "What is your name?"
},
new ReadLine
{
BookmarkName = "UserName",
Result = new OutArgument<string>(name)
},
new WriteLine
{
Text = new InArgument<string>((env) =>
("Hello, " + name.Get(env)))
}
}
};
// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);
// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
idleEvent.Set();
};
// Run the workflow.
wfApp.Run();
// Wait for the workflow to go idle before gathering
// the user's input.
idleEvent.WaitOne();
// Gather the user's input and resume the bookmark.
// Bookmark resumption only occurs when the workflow
// is idle. If a call to ResumeBookmark is made and the workflow
// is not idle, ResumeBookmark blocks until the workflow becomes
// idle before resuming the bookmark.
BookmarkResumptionResult result = wfApp.ResumeBookmark("UserName",
Console.ReadLine());
// Possible BookmarkResumptionResult values:
// Success, NotFound, or NotReady
Console.WriteLine($"BookmarkResumptionResult: {result}");
Quando viene eseguita l'attività ReadLine, crea un Bookmark denominato UserName e quindi attende che il segnalibro venga ripreso. L'host raccoglie i dati desiderati e quindi riprende il Bookmark. Il flusso di lavoro riprende, visualizza il nome e quindi viene completato.
L'applicazione host può esaminare il flusso di lavoro per determinare se sono presenti segnalibri attivi. A tale scopo, è possibile chiamare il metodo GetBookmarks di un'istanza di WorkflowApplication oppure esaminando il WorkflowApplicationIdleEventArgs nel gestore Idle.
L'esempio di codice seguente è simile all'esempio precedente, ad eccezione del fatto che i segnalibri attivi vengono enumerati prima della ripresa del segnalibro. Il flusso di lavoro viene avviato e, una volta creato il Bookmark e divenuto inattivo, viene chiamato GetBookmarks. Al termine del flusso di lavoro, viene visualizzato l'output seguente nella console.
Qual è il tuo nome?
BookmarkName: UserName - OwnerDisplayName: ReadLineSteveCiao, Steve
Variable<string> name = new Variable<string>();
Activity wf = new Sequence
{
Variables = { name },
Activities =
{
new WriteLine
{
Text = "What is your name?"
},
new ReadLine
{
BookmarkName = "UserName",
Result = new OutArgument<string>(name)
},
new WriteLine
{
Text = new InArgument<string>((env) =>
("Hello, " + name.Get(env)))
}
}
};
// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);
// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
// You can also inspect the bookmarks from the Idle handler
// using e.Bookmarks
idleEvent.Set();
};
// Run the workflow.
wfApp.Run();
// Wait for the workflow to go idle and give it a chance
// to create the Bookmark.
idleEvent.WaitOne();
// Inspect the bookmarks
foreach (BookmarkInfo info in wfApp.GetBookmarks())
{
Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
}
// Gather the user's input and resume the bookmark.
wfApp.ResumeBookmark("UserName", Console.ReadLine());
Nell'esempio di codice seguente viene esaminato il WorkflowApplicationIdleEventArgs passato al gestore Idle di un'istanza di WorkflowApplication. In questo esempio, il flusso di lavoro che va in inattività ha un Bookmark con un nome di EnterGuess, di proprietà di un'attività denominata ReadInt. Questo esempio di codice è basato su Procedura: Eseguire un flusso di lavoro, che fa parte dell'esercitazione introduttiva . Se il gestore Idle in tale passaggio viene modificato per contenere il codice di questo esempio, viene visualizzato l'output seguente.
BookmarkName: EnterGuess - OwnerDisplayName: ReadInt
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
foreach (BookmarkInfo info in e.Bookmarks)
{
Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
}
idleEvent.Set();
};
Sommario
WorkflowInvoker offre un modo leggero per richiamare i flussi di lavoro e, sebbene fornisca metodi per il passaggio di dati all'inizio di un flusso di lavoro e l'estrazione di dati da un flusso di lavoro completato, non fornisce scenari più complessi, in cui è possibile usare WorkflowApplication.