Condividi tramite


Creazione di attività asincrone in WF

AsyncCodeActivity fornisce agli autori dell'attività una classe di base che consente alle attività derivate di implementare la logica di esecuzione asincrona. Ciò si rivela utile per attività personalizzate che devono eseguire un lavoro asincrono senza contenere il thread dell'utilità di pianificazione del flusso di lavoro e senza bloccare nessuna attività che può essere eseguita in parallelo. In questo argomento viene fornita una panoramica su come creare attività asincrone personalizzate usando l'oggetto AsyncCodeActivity.

Uso di AsyncCodeActivity

L'oggetto System.Activities fornisce agli autori dell'attività personalizzata classi di base diverse per requisiti di creazione di attività differenti. Ognuna presenta una particolare semantica e offre a un autore del flusso di lavoro (e al runtime attività) un contratto corrispondente. Un'attività basata sull'oggetto AsyncCodeActivity è un'attività che esegue un lavoro in modo asincrono relativo al thread dell'utilità di pianificazione e la cui logica di esecuzione viene espressa in codice gestito. Come conseguenza di tale modalità asincrona, un oggetto AsyncCodeActivity può determinare un punto di inattività durante l'esecuzione. A causa della natura volatile del lavoro asincrono, un oggetto AsyncCodeActivity crea sempre un blocco di non persistenza per la durata dell'esecuzione dell'attività. In questo modo si evita che l'esecuzione del flusso di lavoro renda persistente l'istanza del flusso di lavoro durante il lavoro asincrono e si evita anche lo scaricamento dell'istanza del flusso di lavoro mentre il codice asincrono è in esecuzione.

Metodi AsyncCodeActivity

Le attività che derivano dall'oggetto AsyncCodeActivity possono creare la logica di esecuzione asincrona eseguendo l'override dei metodi BeginExecute e EndExecute con codice personalizzato. Quando chiamati dal runtime, a questi metodi viene passato un oggetto AsyncCodeActivityContext. AsyncCodeActivityContext consente all'autore dell'attività di fornire lo stato condiviso attraverso BeginExecute/ EndExecute nella proprietà UserState del contesto. Nell'esempio seguente un'attività GenerateRandom genera in modo asincrono un numero casuale.

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

L'attività dell'esempio precedente deriva da AsyncCodeActivity<TResult> e dispone di un oggetto OutArgument<int> elevato denominato Result. Il valore restituito dal metodo GetRandom viene estratto e restituito dall'override del metodo EndExecute e tale valore viene impostato come valore Result. Le attività asincrone che non restituiscono un risultato devono derivare da AsyncCodeActivity. Nell'esempio seguente viene definita un'attività DisplayRandom che deriva da AsyncCodeActivity. Questa attività è come l'attività GetRandom ma anziché restituire un risultato visualizza un messaggio nella 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: {0}", r.Next(1, 101));
    }
}

Notare che a causa dell'assenza di un valore restituito, DisplayRandom usa Action anziché Func<T,TResult> per richiamare il delegato e il delegato non restituisce alcun valore.

Anche AsyncCodeActivity fornisce un override di Cancel. Mentre BeginExecute e EndExecute sono override obbligatori, Cancel è facoltativo e può essere sottoposto a override in modo che l'attività possa pulire il relativo stato asincrono in attesa quando è in fase di annullamento o di interruzione. Se la pulizia è possibile e l'oggetto AsyncCodeActivity.ExecutingActivityInstance.IsCancellationRequested è true, l'attività deve chiamare il metodo MarkCanceled. Qualsiasi eccezione generata da questo metodo è irreversibile per l'istanza del flusso di lavoro.

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

Richiamo di metodi asincroni su una classe

Molte delle classi in .NET Framework dispongono di una funzionalità asincrona che può essere richiamata in modo asincrono tramite un'attività basata su AsyncCodeActivity. Nell'esempio seguente, viene creata un'attività che crea in modo asincrono un file tramite la classe 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();
        }
    }
}

Condivisione dello stato tra i metodi BeginExecute e EndExecute

Nell'esempio precedente, è stato effettuato l'accesso in FileStream all'oggetto BeginExecute creato in EndExecute. Ciò è possibile poiché la variabile file è stata passata nella proprietà AsyncCodeActivityContext.UserState in BeginExecute. Questo è il metodo corretto per condividere lo stato tra BeginExecute e EndExecute. È errato usare una variabile membro nella classe derivata (in questo caso FileWriter) per condividere lo stato tra BeginExecute e EndExecute perché più istanze dell'attività possono fare riferimento agli oggetti attività. Tentare di usare una variabile membro per condividere lo stato può causare la sovrascrittura o la cancellazione di valori da un oggetto ActivityInstance a un altro oggetto ActivityInstance.

Accesso ai valori degli argomenti

L'ambiente di un oggetto AsyncCodeActivity è costituito dagli argomenti definiti nell'attività. L'accesso a questi argomenti può essere eseguito dagli override BeginExecute/EndExecute usando il parametro AsyncCodeActivityContext. L'accesso agli argomenti non può essere eseguito nel delegato, ma i valori dell'argomento o i dati desiderati possono essere passati al delegato usando i relativi parametri. Nell'esempio seguente viene definita un'attività che genera un numero casuale e ottiene il limite superiore incluso dal relativo argomento Max. Il valore dell'argomento viene passato al codice asincrono quando viene richiamato il delegato.

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

Azioni di programmazione o attività figlio usando AsyncCodeActivity

Le attività personalizzate derivate AsyncCodeActivity forniscono un metodo per eseguire il lavoro in modo asincrono rispetto al thread del flusso di lavoro, ma non consentono di pianificare le attività figlio o le azioni. Tuttavia, il comportamento asincrono può essere incorporato nella pianificazione delle attività figlio tramite la composizione. Un'attività asincrona può essere creata e quindi composta con un'attività Activity o NativeActivity derivata per fornire il comportamento asincrono e la pianificazione delle attività figlio o delle azioni. Ad esempio, è possibile creare un'attività che deriva da Activity e ha come implementazione un oggetto Sequence che contiene l'attività asincrona nonché le altre attività che implementano la logica dell'attività. Per altri esempi sulla composizione di attività tramite Activity e NativeActivity, vedere Procedura: Creare un'attività e Opzioni di creazione di attività.

Vedi anche