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


Создание асинхронных действий в 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: {r.Next(1, 101)}");
    }
}

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

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 см. в разделах "Практическое руководство: создание действий" и "Варианты разработки действий".

См. также