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 WebRequest
s 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
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 await
dans 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" .
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 ());
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++;
}
});
}
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()
.
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.