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