Crear actividades asincrónicas en WF
AsyncCodeActivity proporciona a los autores de actividad el uso de una clase base que permite a las actividades derivadas implementar la lógica de ejecución asincrónica. Resulta útil para las actividades personalizadas que deben realizar el trabajo asincrónico sin retener el subproceso de programador de flujo de trabajo y bloquear cualquier actividad que pueda ejecutarse en paralelo. En este tema se proporciona información general de cómo crear actividades asincrónicas personalizadas mediante AsyncCodeActivity.
Usar AsyncCodeActivity
System.Activities proporciona a los autores de actividad personalizada clases base diferentes para distintos requisitos de creación de actividades. Cada uno posee una semántica particular y proporciona al autor del flujo de trabajo (y al tiempo de ejecución de la actividad) el contrato correspondiente. Una actividad basada en AsyncCodeActivity es aquélla que realiza el trabajo de manera asincrónica según el subproceso del programador y cuya lógica de ejecución se expresa en código administrado. Como resultado de la asincronía, AsyncCodeActivity puede inducir un punto inactivo durante la ejecución. Debido a la naturaleza volátil del trabajo asincrónico, AsyncCodeActivity crea siempre un bloque de no persistencia durante la ejecución de la actividad. Esto evita que el tiempo de ejecución del flujo de trabajo conserve la instancia de flujo de trabajo en medio del trabajo asincrónico y también evita que la instancia de flujo de trabajo se descargue mientras el código asincrónico se está ejecutando.
Métodos AsyncCodeActivity
Las actividades que derivan de AsyncCodeActivity pueden crear la lógica de ejecución asincrónica reemplazando los métodos BeginExecute y EndExecute con código personalizado. Cuando los llama el tiempo de ejecución, se pasa un AsyncCodeActivityContext a estos métodos. AsyncCodeActivityContext permite que el autor de la actividad proporcione el estado compartido a través de BeginExecute/ EndExecute en la propiedad UserState del contexto. En el siguiente ejemplo, una actividad GenerateRandom
genera un número aleatorio de forma asincrónica.
public sealed class GenerateRandom : AsyncCodeActivity<int>
{
static Random r = new Random();
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
// Create a delegate that references the method that implements
// the asynchronous work. Assign the delegate to the UserState,
// invoke the delegate, and return the resulting IAsyncResult.
Func<int> GetRandomDelegate = new Func<int>(GetRandom);
context.UserState = GetRandomDelegate;
return GetRandomDelegate.BeginInvoke(callback, state);
}
protected override int EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
// Get the delegate from the UserState and call EndInvoke
Func<int> GetRandomDelegate = (Func<int>)context.UserState;
return (int)GetRandomDelegate.EndInvoke(result);
}
int GetRandom()
{
// This activity simulates taking a few moments
// to generate the random number. This code runs
// asynchronously with respect to the workflow thread.
Thread.Sleep(5000);
return r.Next(1, 101);
}
}
La actividad de ejemplo anterior deriva de AsyncCodeActivity<TResult>y tiene un OutArgument<int>
elevado denominado Result
. El valor devuelto por el método GetRandom
se extrae y, a su vez, lo devuelve el reemplazo del EndExecute. Este valor se establece como el valor Result
. Las actividades asincrónicas que no devuelvan un resultado deberían derivar de AsyncCodeActivity. En el siguiente ejemplo, se define una actividad DisplayRandom
que deriva de AsyncCodeActivity. Esta actividad es similar a la actividad GetRandom
, pero en lugar de devolver un resultado muestra un mensaje en la consola.
public sealed class DisplayRandom : AsyncCodeActivity
{
static Random r = new Random();
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
// Create a delegate that references the method that implements
// the asynchronous work. Assign the delegate to the UserState,
// invoke the delegate, and return the resulting IAsyncResult.
Action GetRandomDelegate = new Action(GetRandom);
context.UserState = GetRandomDelegate;
return GetRandomDelegate.BeginInvoke(callback, state);
}
protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
// Get the delegate from the UserState and call EndInvoke
Action GetRandomDelegate = (Action)context.UserState;
GetRandomDelegate.EndInvoke(result);
}
void GetRandom()
{
// This activity simulates taking a few moments
// to generate the random number. This code runs
// asynchronously with respect to the workflow thread.
Thread.Sleep(5000);
Console.WriteLine("Random Number: {0}", r.Next(1, 101));
}
}
Tenga en cuenta que dado que no hay ningún valor devuelto, DisplayRandom
utiliza Action en lugar de Func<T,TResult> para invocar su delegado y el delegado no devuelve ningún valor.
AsyncCodeActivity también proporciona el reemplazo de Cancel. Aunque BeginExecute y EndExecute son reemplazos necesarios, Cancel es opcional y puede reemplazarse de manera que la actividad pueda limpiar el estado asincrónico pendiente cuando se cancele o anule. Si la limpieza es posible y AsyncCodeActivity.ExecutingActivityInstance.IsCancellationRequested
es true
, la actividad debería llamar a MarkCanceled. Cualquier excepción que se inicie a partir de este método será grave para la instancia del flujo de trabajo.
protected override void Cancel(AsyncCodeActivityContext context)
{
// Implement any cleanup as a result of the asynchronous work
// being canceled, and then call MarkCanceled.
if (context.IsCancellationRequested)
{
context.MarkCanceled();
}
}
Invocar métodos asincrónicos en una clase
Muchas de las clases de .NET Framework proporcionan funcionalidad asincrónica que, a su vez, puede invocarse de forma asincrónica mediante una actividad basada en AsyncCodeActivity. En el siguiente ejemplo, se crea una actividad que crea un archivo de forma asincrónica usando la clase FileStream.
public sealed class FileWriter : AsyncCodeActivity
{
public FileWriter()
: base()
{
}
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
string tempFileName = Path.GetTempFileName();
Console.WriteLine("Writing to file: " + tempFileName);
FileStream file = File.Open(tempFileName, FileMode.Create);
context.UserState = file;
byte[] bytes = UnicodeEncoding.Unicode.GetBytes("123456789");
return file.BeginWrite(bytes, 0, bytes.Length, callback, state);
}
protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
FileStream file = (FileStream)context.UserState;
try
{
file.EndWrite(result);
file.Flush();
}
finally
{
file.Close();
}
}
}
Compartir el estado entre los métodos BeginExecute y EndExecute
En el ejemplo anterior, se tuvo acceso en FileStream al objeto BeginExecute creado en EndExecute. Esto es posible porque la variable file
se pasó en la propiedad AsyncCodeActivityContext.UserState en BeginExecute. Este es el método correcto para compartir el estado entre BeginExecute y EndExecute. Es incorrecto usar una variable miembro en la clase derivada (FileWriter
en este caso) para compartir el estado entre el BeginExecute y EndExecute porque varias instancias de actividad pueden hacer referencia al objeto de actividad. Si se intenta usar una variable miembro para compartir el estado puede dar lugar a valores de una ActivityInstance que sobrescriban o que usen valores de otra ActivityInstance.
Acceso a valores de argumento
El entorno de AsyncCodeActivity está compuesto de los argumentos definidos en la actividad. Se puede tener acceso a los argumentos desde los reemplazos BeginExecute/EndExecute usando el parámetro AsyncCodeActivityContext. No se puede tener acceso a los argumentos en el delegado, pero los valores de argumento o cualquier otro dato deseado se pueden pasar al delegado con sus parámetros. En el siguiente ejemplo, se define una actividad que genera números aleatorios y que obtiene el enlace superior inclusivo del argumento Max
. El valor del argumento se pasa al código asincrónico cuando se invoca el delegado.
public sealed class GenerateRandomMax : AsyncCodeActivity<int>
{
public InArgument<int> Max { get; set; }
static Random r = new Random();
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
// Create a delegate that references the method that implements
// the asynchronous work. Assign the delegate to the UserState,
// invoke the delegate, and return the resulting IAsyncResult.
Func<int, int> GetRandomDelegate = new Func<int, int>(GetRandom);
context.UserState = GetRandomDelegate;
return GetRandomDelegate.BeginInvoke(Max.Get(context), callback, state);
}
protected override int EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
// Get the delegate from the UserState and call EndInvoke
Func<int, int> GetRandomDelegate = (Func<int, int>)context.UserState;
return (int)GetRandomDelegate.EndInvoke(result);
}
int GetRandom(int max)
{
// This activity simulates taking a few moments
// to generate the random number. This code runs
// asynchronously with respect to the workflow thread.
Thread.Sleep(5000);
return r.Next(1, max + 1);
}
}
Programar acciones o actividades secundarias mediante AsyncCodeActivity
Las actividades personalizadas derivadas de AsyncCodeActivity proporcionan un método para realizar el trabajo de forma asincrónica con respecto al subproceso del flujo de trabajo, pero no proporcionan la capacidad de programar actividades o acciones secundarias. Sin embargo, el comportamiento asincrónico se puede incorporar con la programación de actividades secundarias a través de composición. Una actividad asincrónica se puede crear, y después componer con una Activity o una actividad derivada de NativeActivity para proporcionar comportamiento asincrónico y programación de actividades o acciones secundarias. Por ejemplo, se podría crear una actividad que derive de Activity y tenga como su implementación una Sequence que contenga la actividad asincrónica, así como otras actividades que implementen la lógica de la actividad. Para obtener más ejemplos de composición de actividades con Activity y NativeActivity, consulte Procedimiento para crear una actividad y Opciones de creación de actividades en WF.