Поделиться через


Создание асинхронных действий в WF

Класс AsyncCodeActivity предоставляет авторам действий базовый класс, обеспечивающий реализацию логики асинхронного выполнения в производных от него действиях. Это особенно полезно для пользовательских действий, которые должны выполнять работу асинхронно, не останавливая поток расписания рабочих процессов и не блокируя другие действия, которые могут выполняться параллельно. В данном разделе приводятся общие сведения о процессе создания пользовательских асинхронных действий с использованием AsyncCodeActivity.

Использование AsyncCodeActivity

System.Activities предоставляет авторам пользовательских действий различные базовые классы для различных требований, предъявляемых при создании действий. Каждый класс имеет определенную семантику и предоставляет автору рабочего процесса (и среде выполнения действия) соответствующий контракт. Действие, основанное на AsyncCodeActivity - это действие, выполняющее работу асинхронно относительно потока планировщика, а логика выполнения такого действия выражена в управляемом коде. Вследствие асинхронности AsyncCodeActivity может вызывать точку бездействия во время выполнения. Из-за нестабильной природы асинхронной работы AsyncCodeActivity всегда создается не сохраняемый блок в течение длительности выполнения действия. Это позволяет защитить среду выполнения рабочего процесса от сохранения экземпляра рабочего процесса во время выполнения асинхронной работы, а также предотвратить выгрузку экземпляра рабочего процесса во время выполнения асинхронного кода.

Методы AsyncCodeActivity

Действия, производные от AsyncCodeActivity, могут создавать логику асинхронного выполнения перепрограммированием метода BeginExecute и методов интерфейса EndExecute. При вызове средой выполнения эти методы передаются AsyncCodeActivityContext. AsyncCodeActivityContextпозволяет автору действий предоставлять общее состояние BeginExecute/ EndExecute в свойстве контекста.UserState В следующем примере действие GenerateRandom асинхронно формирует случайное число.

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

Действие, описанное в предыдущем примере, является производным от AsyncCodeActivity<TResult>, имеет аргумент более высокого уровня OutArgument<int> с именем Result. Значение, возвращаемое методом GetRandom, извлекается и возвращается путем переопределения EndExecute, после чего это значение задается как значение Result. Асинхронные действия, не возвращающие результат, должны наследоваться от AsyncCodeActivity. В следующем примере, определяется действие DisplayRandom, являющееся производным от AsyncCodeActivity. Это действие подобно действию GetRandom, однако вместо возвращения результата оно отображает сообщение на экране консоли.

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

Обратите внимание, что из-за отсутствия возвращаемого значения действие DisplayRandom использует класс Action вместо Func<T,TResult> для вызова своего делегата, а делегат не возвращает значение.

AsyncCodeActivity также предоставляет переопределение Cancel. В то время как BeginExecute и EndExecute являются обязательными переопределениями, Cancel является необязательным и может быть переопределен, чтобы действие могло очистить необработанное асинхронное состояние в случае своей отмены или прерывания. Если очистка возможна и AsyncCodeActivity.ExecutingActivityInstance.IsCancellationRequested имеет значение true, то действие должно вызвать MarkCanceled. Любые исключения, возникшие при выполнении данного метода, являются неустранимыми для экземпляра рабочего процесса.

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

Вызов асинхронных методов в классе

Многие классы в платформа .NET Framework предоставляют асинхронную функциональность, и эту функцию можно асинхронно вызывать с помощью AsyncCodeActivity действия на основе. В следующем примере создается действие, которое асинхронно создает файл с помощью 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();
        }
    }
}

Общее состояние между методами BeginExecute и EndExecute

В предыдущем примере доступ к объекту FileStream, созданному в BeginExecute, был выполнен в EndExecute. Это обусловлено тем, что переменная file была передана в свойстве AsyncCodeActivityContext.UserState методом BeginExecute. Это правильный метод для совместного использования состояния между BeginExecute и EndExecute. Использовать переменную-член в производном классе (в данном случае FileWriter) для совместного использования состояния между BeginExecute и EndExecute неверно, так как на объект действия могут ссылаться несколько экземпляров действия. Попытка использовать переменную-член для совместного использования состояния может привести к перезаписи значений из одного ActivityInstance или потреблению значений из другого ActivityInstance.

Доступ к значениям аргументов

Среда AsyncCodeActivity состоит из аргументов, определенных для действия. К этим аргументам можно получить доступ из BeginExecute/EndExecute переопределений AsyncCodeActivityContext с помощью параметра. Получить доступ к аргументам в делегате невозможно, но значения аргументов или любые другие требуемые данные можно передать в делегат через его параметры. В следующем примере определяется действие создания случайного числа, которое получает инклюзивную верхнюю границу создаваемого числа в аргументе Max. При вызове делегата значение аргумента передается в асинхронный код.

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

Планирование действий или дочерних действий с помощью AsyncCodeActivity

Производные от AsyncCodeActivity настраиваемых действий предоставляют метод для асинхронного выполнения работы в отношении к потоку рабочего процесса, но не предоставляют возможности планирования действий или дочерних действий. Однако асинхронное поведение можно встроить в планирование дочерних действий посредством композиции. Асинхронное действие можно создать, а затем сочетать с действием, производным от Activity или NativeActivity, предоставив асинхронное поведение и планирование действий или дочерних действий. Например, можно создать действие, производное от Activity, у которого в качестве его реализации будет Sequence, содержащий асинхронное действие, а также другие действия, реализующие логику этого действия. Дополнительные примеры создания действий с использованием Activity и NativeActivityсм. в разделе "Практическое руководство. Создание параметров разработки действий и действий".

См. также