web-dev-qa-db-fra.com

Démarrer une méthode asynchrone en tant que thread ou tâche

Je suis nouveau sur C#s await/async et je suis en train de jouer un peu.

Dans mon scénario, j'ai un objet client simple qui a une propriété WebRequest. Le client devrait envoyer périodiquement des messages vivants sur la WebRequests RequestStream. Il s'agit du constructeur de l'objet client:

public Client()
{
    _webRequest = WebRequest.Create("some url");
    _webRequest.Method = "POST";

    IsRunning = true;

    // --> how to start the 'async' method (see below)
}

et la méthode async alive-sender

private async void SendAliveMessageAsync()
{
    const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
    var seconds = 0;
    while (IsRunning)
    {
        if (seconds % 10 == 0)
        {
            await new StreamWriter(_webRequest.GetRequestStream()).WriteLineAsync(keepAliveMessage);
        }

        await Task.Delay(1000);
        seconds++;
    }
}

Comment faut-il commencer la méthode?

nouveau thread (SendAliveMessageAsync) .Start ();

ou

Task.Run (SendAliveMessageAsync); // changer le type de retour en tâche

ou

attend SendAliveMessageAsync (); // échoue dès que le constructeur n'est pas asynchrone

Ma question concerne davantage ma compréhension personnelle de await/async qui, je suppose, peut être erronée à certains égards.

La troisième option jette

The 'await' operator can only be used in a method or lambda marked with the 'async' modifier
6
KingKerosin

Comment faut-il commencer la méthode?

Je vote pour "rien de ce qui précède". :)

"Feu et oublie" est un scénario difficile à gérer correctement. En particulier, la gestion des erreurs est toujours problématique. Dans ce cas, async void peut vous surprendre.

Je préfère enregistrer explicitement les tâches si je ne les awaitdans pas immédiatement:

private async Task SendAliveMessageAsync();

public Task KeepaliveTask { get; private set; }

public Client()
{
  ...
  KeepaliveTask = SendAliveMessageAsync();
}

Cela permet au moins aux utilisateurs de Client de détecter et de récupérer les exceptions générées par la méthode SendAliveMessageAsync.

Sur une note de côté, ce modèle est presque équivalent à mon modèle "initialisation asynchrone" .

7
Stephen Cleary

Edité comme réponse précédente était faux:

Comme c'est dans le constructeur, je pense que vous auriez à filer un nouveau fil pour cela. Personnellement, je le ferais en utilisant

Task.Factory.StartNew (() => SendAliveMessageAsync ());

3
John Clifford

Puisqu’il s’agit d’un incendie et oublier opération, vous devriez le démarrer avec 

SendAliveMessageAsync();

Notez que await ne démarre pas une nouvelle Task. Attendre la fin d'une Task est simplement un sucre syntaxique.
Un nouveau thread est lancé avec Task.Run.

Donc, dans SendAliveMessageAsync, vous devriez commencer une nouvelle tâche:

private async Task SendAliveMessageAsync()
{
    const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
    await Task.Run( () => {
        var seconds = 0;
        while (IsRunning)
        {
            if (seconds % 10 == 0)
            {
                await new StreamWriter(_webRequest.GetRequestStream()).WriteLineAsync(keepAliveMessage);
            }

            await Task.Delay(1000);
            seconds++;
        }
    });
}
0
Domysee

Voici une option, car vous ne pouvez pas appeler await de l'intérieur d'un constructeur.

Je suggérerais d'utiliser le cadre réactif de Microsoft (NuGet "Rx-Main").

Le code ressemblerait à ceci:

public class Client
{
    System.Net.WebRequest _webRequest = null;
    IDisposable _subscription = null;

    public Client()
    {
        _webRequest = System.Net.WebRequest.Create("some url");
        _webRequest.Method = "POST";

        const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";

        var keepAlives =
            from n in Observable.Interval(TimeSpan.FromSeconds(10.0))
            from u in Observable.Using(
                () => new StreamWriter(_webRequest.GetRequestStream()),
                sw => Observable.FromAsync(() => sw.WriteLineAsync(keepAliveMessage)))
            select u;

        _subscription = keepAlives.Subscribe();
    }
}

Ce code gère tous les threads requis et élimine correctement la variable StreamWriter au fur et à mesure.

À chaque fois que vous souhaitez arrêter le Keep Alives, appelez simplement _subscription.Dispose().

0
Enigmativity

Dans votre code, il n'est pas nécessaire d'utiliser async/wait, il vous suffit de configurer un nouveau thread pour effectuer de longues opérations.

private void SendAliveMessage()
{
    const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
    var sreamWriter = new StreamWriter(_webRequest.GetRequestStream());
    while (IsRunning)
    {
        sreamWriter.WriteLine(keepAliveMessage);
        Thread.Sleep(10 * 1000);
    }    
}

Utilisation de Task.Factory.StartNew(SendAliveMessage, TaskCreationOptions.LongRunning) pour effectuer l'opération.

Si vous voulez vraiment utiliser async/wait, appelez-le simplement dans constructeur sans le modificateur wait et le mot de passe oublié.

public Client()
{
    _webRequest = WebRequest.Create("some url");
    _webRequest.Method = "POST";

    IsRunning = true;

    SendAliveMessageAsync();    //just call it and forget it.
}

Je pense que ce n'est pas une bonne idée de mettre en place un fil de discussion long ou d'utiliser un motif async/wait. Les minuteries peuvent être plus appropriées dans cette situation.

0
cFish