Compartilhar via


Criando atividades assíncronas no WF

AsyncCodeActivity fornece aos autores de atividade uma classe base a ser usada que permite que atividades derivadas implementem a lógica de execução assíncrona. Isso é útil para atividades personalizadas que devem executar um trabalho assíncrono sem manter o thread do agendador de fluxo de trabalho e bloquear quaisquer atividades que possam ser executadas em paralelo. Este tópico fornece uma visão geral de como criar atividades assíncronas personalizadas usando AsyncCodeActivity.

Usando AsyncCodeActivity

System.Activities fornece autores de atividades personalizadas com classes base diferentes para diferentes requisitos de criação de atividades. Cada um deles carrega uma semântica específica e fornece ao autor de fluxo de trabalho (e ao tempo de execução da atividade) um contrato correspondente. Uma AsyncCodeActivity atividade baseada é uma atividade que executa o trabalho de forma assíncrona em relação ao thread do agendador e cuja lógica de execução é expressa no código gerenciado. Como resultado de tornar-se assíncrono, um AsyncCodeActivity pode induzir um estado ocioso durante a execução. Devido à natureza volátil do trabalho assíncrono, um AsyncCodeActivity sempre cria um bloco sem persistência durante a execução da atividade. Isso evita que o tempo de execução do fluxo de trabalho persista a instância do fluxo de trabalho no meio do trabalho assíncrono e também evita que a instância do fluxo de trabalho seja descarregada enquanto o código assíncrono está sendo executado.

Métodos AsyncCodeActivity

Atividades derivadas de AsyncCodeActivity podem criar lógica de execução assíncrona ao substituir os métodos BeginExecute e EndExecute por código personalizado. Quando chamados pelo tempo de execução, esses métodos recebem um AsyncCodeActivityContext. AsyncCodeActivityContext permite que o autor da atividade forneça um estado compartilhado na propriedade UserState do contexto BeginExecute/ EndExecute. No exemplo a seguir, uma GenerateRandom atividade gera um número aleatório de forma assíncrona.

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);
    }
}

A atividade de exemplo anterior deriva de AsyncCodeActivity<TResult> e possui um OutArgument<int> elevado, denominado Result. O valor retornado pelo GetRandom método é extraído e retornado pela EndExecute substituição e esse valor é definido como o Result valor. Atividades assíncronas que não retornam um resultado devem derivar de AsyncCodeActivity. No exemplo a seguir, é definida uma DisplayRandom atividade que deriva de AsyncCodeActivity. Essa atividade é como a GetRandom atividade, mas em vez de retornar um resultado, ela exibe uma mensagem para o console.

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: {r.Next(1, 101)}");
    }
}

Observe que, como não há nenhum valor retornado, DisplayRandom usa um Action em vez de um Func<T,TResult> para invocar seu delegado e o delegado não retorna nenhum valor.

AsyncCodeActivity também fornece uma Cancel substituição. Embora BeginExecute e EndExecute sejam necessárias substituições, Cancel é opcional e pode ser substituída para que a atividade possa limpar seu estado assíncrono pendente quando estiver sendo cancelada ou anulada. Se a limpeza for possível e AsyncCodeActivity.ExecutingActivityInstance.IsCancellationRequested for true, a atividade deverá chamar MarkCanceled. Todas as exceções lançadas por este método são fatais para a instância do workflow.

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();
    }
}

Invocando métodos assíncronos em uma classe

Muitas das classes no .NET Framework fornecem funcionalidade assíncrona e essa funcionalidade pode ser invocada de forma assíncrona usando uma AsyncCodeActivity atividade baseada. No exemplo a seguir, é criada uma atividade que cria de forma assíncrona um arquivo usando a FileStream classe.

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();
        }
    }
}

Compartilhamento de estado entre os métodos BeginExecute e EndExecute

No exemplo anterior, o objeto FileStream que foi criado em BeginExecute foi acessado em EndExecute. Isso é possível porque a file variável foi passada na AsyncCodeActivityContext.UserState propriedade em BeginExecute. Esse é o método correto para compartilhar o estado entre BeginExecute e EndExecute. É incorreto usar uma variável de membro na classe derivada (FileWriter nesse caso) para compartilhar o estado entre BeginExecute e EndExecute porque o objeto de atividade pode ser referenciado por várias instâncias de atividade. A tentativa de usar uma variável de membro para compartilhar o estado pode resultar em valores de uma ActivityInstance sobrescrevendo ou consumindo valores de outra ActivityInstance.

Acessando valores de argumento

O ambiente de um AsyncCodeActivity consiste nos argumentos definidos na atividade. Esses argumentos podem ser acessados das BeginExecute/EndExecute substituições usando o AsyncCodeActivityContext parâmetro. Os argumentos não podem ser acessados no delegado, mas os valores de argumento ou quaisquer outros dados desejados podem ser passados para o delegado usando seus parâmetros. No exemplo a seguir, uma atividade aleatória de geração de números é definida que obtém o limite superior inclusivo de seu Max argumento. O valor do argumento é passado para o código assíncrono quando o delegado é invocado.

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);
    }
}

Agendando ações ou atividades-filhas usando AsyncCodeActivity

AsyncCodeActivity as atividades personalizadas derivadas fornecem um método para executar o trabalho de forma assíncrona em relação ao thread de fluxo de trabalho, mas não fornecem a capacidade de agendar atividades ou ações filho. No entanto, o comportamento assíncrono pode ser incorporado ao agendamento de atividades filho por meio da composição. Uma atividade assíncrona pode ser criada e então composta com uma atividade derivada de Activity ou NativeActivity para fornecer comportamento assíncrono e agendamento de atividades ou ações filhas. Por exemplo, uma atividade pode ser criada que deriva de Activity, e tem como implementação uma Sequence atividade que contém a atividade assíncrona, bem como as outras atividades que implementam a lógica da atividade. Para obter mais exemplos de atividades de composição usando Activity e NativeActivity, consulte Como criar uma atividade e opções de criação de atividades.

Consulte também