Création d’activités asynchrones dans WF

AsyncCodeActivity fournit aux auteurs d’activités une classe de base à utiliser qui permet aux activités dérivées d’implémenter une logique d’exécution asynchrone. Cela est utile pour les activités personnalisées qui doivent effectuer un travail asynchrone sans contenir le thread du planificateur de flux de travail et bloquer les activités qui peuvent être exécutées en parallèle. Cette rubrique fournit une vue d’ensemble de la création d’activités asynchrones personnalisées à l’aide de AsyncCodeActivity.

Utilisation d’AsyncCodeActivity

System.Activities fournit des auteurs d’activités personnalisés avec différentes classes de base pour différentes exigences de création d’activité. Chacun d’eux porte une sémantique particulière et offre à un auteur de flux de travail ainsi qu'au moteur d'exécution des activités un contrat correspondant. Une activité AsyncCodeActivity basée est une activité qui effectue un travail de manière asynchrone par rapport au fil d'exécution du planificateur et dont la logique d’exécution est exprimée dans le code géré. En raison de l'exécution asynchrone, un AsyncCodeActivity peut induire un point d'inactivité pendant l'exécution. En raison de la nature volatile du travail asynchrone, un AsyncCodeActivity bloc sans persistance est toujours créé durant l’exécution de l’activité. Cela empêche le runtime de flux de travail de conserver l’instance de flux de travail au milieu du travail asynchrone et empêche également l’instance de workflow de se décharger pendant l’exécution du code asynchrone.

Méthodes AsyncCodeActivity

Les activités dérivées de AsyncCodeActivity peuvent créer une logique d'exécution asynchrone en remplaçant les méthodes BeginExecute et EndExecute par du code personnalisé. Lorsqu'elles sont appelées par le runtime, ces méthodes reçoivent un AsyncCodeActivityContext. AsyncCodeActivityContext permet à l'auteur de l'activité de fournir un état partagé BeginExecute/ EndExecute dans la propriété UserState du contexte. Dans l’exemple suivant, une GenerateRandom activité génère un nombre aléatoire de manière asynchrone.

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’exemple d’activité précédent dérive de AsyncCodeActivity<TResult>, et possède un OutArgument<int> élevé nommé Result. La valeur retournée par la méthode GetRandom est extraite et retournée par la surcharge EndExecute, et cette valeur est définie comme la valeur Result. Les activités asynchrones qui ne retournent pas de résultat doivent dériver de AsyncCodeActivity. Dans l'exemple suivant, une activité DisplayRandom est définie, dérivant de AsyncCodeActivity. Cette activité est similaire à l’activité GetRandom , mais au lieu de renvoyer un résultat, elle affiche un message dans la 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)}");
    }
}

Notez que, étant donné qu’il n’existe aucune valeur de retour, DisplayRandom utilise une Action valeur au lieu d’un Func<T,TResult> pour appeler son délégué et que le délégué ne retourne aucune valeur.

AsyncCodeActivity fournit également un Cancel remplacement. Bien que BeginExecute et EndExecute soient des remplacements obligatoires, Cancel est facultatif et peut être remplacé afin que l'activité puisse nettoyer son état asynchrone en attente lorsqu’elle est annulée ou abandonnée. Si le nettoyage est possible et AsyncCodeActivity.ExecutingActivityInstance.IsCancellationRequested est true, l’activité doit appeler MarkCanceled. Toute exception levée à partir de cette méthode est fatale pour l’instance de 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();
    }
}

Appel de méthodes asynchrones sur une classe

La plupart des classes du .NET Framework fournissent des fonctionnalités asynchrones, et cette fonctionnalité peut être appelée de manière asynchrone à l’aide d’une activité basée sur AsyncCodeActivity. Dans l’exemple suivant, une activité est créée de façon asynchrone qui crée un fichier à l’aide de la 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();
        }
    }
}

Partage d’état entre les méthodes BeginExecute et EndExecute

Dans l’exemple précédent, l’objet FileStream qui a été créé à BeginExecute a été accessible dans le EndExecute. Cela est possible, car la file variable a été passée dans la AsyncCodeActivityContext.UserState propriété dans BeginExecute. Il s’agit de la méthode correcte pour le partage d’état entre BeginExecute et EndExecute. Il est incorrect d’utiliser une variable membre dans la classe dérivée (FileWriter dans ce cas) pour partager l’état entre BeginExecute et EndExecute parce que l’objet d’activité peut être référencé par plusieurs instances d’activité. Toute tentative d’utilisation d’une variable membre pour partager l’état peut entraîner des valeurs d’un ActivityInstance écrasant ou consommant des valeurs d’une autre ActivityInstance.

Accès aux valeurs d’argument

L’environnement d’un AsyncCodeActivity se compose des arguments définis sur l’activité. Ces arguments sont accessibles à partir des substitutions BeginExecute/EndExecute en utilisant le paramètre AsyncCodeActivityContext. Les arguments ne sont pas accessibles dans le délégué, mais les valeurs d’argument ou toutes les autres données souhaitées peuvent être transmises au délégué à l’aide de ses paramètres. Dans l’exemple suivant, une activité de génération de nombres aléatoires est définie qui obtient la limite supérieure inclusive de son Max argument. La valeur de l’argument est passée au code asynchrone lorsque le délégué est appelé.

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

Planification d’actions ou d’activités enfants à l’aide d’AsyncCodeActivity

AsyncCodeActivity Les activités personnalisées dérivées offrent une méthode permettant d'exécuter des tâches de manière asynchrone par rapport au thread de workflow, mais ne permettent pas de planifier des activités ou des actions enfants. Toutefois, le comportement asynchrone peut être incorporé à la planification des activités enfants par le biais de la composition. Une activité asynchrone peut être créée, puis composée avec une activité dérivée Activity ou NativeActivity pour fournir un comportement et une planification asynchrones des activités ou actions enfants. Par exemple, une activité peut être créée qui dérive de Activity, et a comme implémentation une Sequence activité contenant l’activité asynchrone ainsi que les autres activités qui implémentent la logique de l’activité. Pour obtenir d’autres exemples de composition d’activités à l’aide Activity et NativeActivity, consultez Comment : créer une activité et des options de création d’activité.

Voir également