可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm new to C#
s await/async
and currently playing around a bit.
In my scenario I have a simple client-object which has a WebRequest
property. The client should send periodically alive-messages over the WebRequest
s RequestStream
.
This is the constructor of the client-object:
public Client()
{
_webRequest = WebRequest.Create("some url");
_webRequest.Method = "POST";
IsRunning = true;
// --> how to start the 'async' method (see below)
}
and the async alive-sender method
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++;
}
}
How should the method be started?
new Thread(SendAliveMessageAsync).Start();
or
Task.Run(SendAliveMessageAsync); // changing the returning type to Task
or
await SendAliveMessageAsync(); // fails as of the constructor is not async
My question is more about my personal understanding of await/async
which I guess may be wrong in some points.
The third option is throwing
The 'await' operator can only be used in a method or lambda marked with the 'async' modifier
回答1:
How should the method be started?
I vote for "none of the above". :)
"Fire and forget" is a difficult scenario to handle correctly. In particular, error handling is always problematic. In this case, async void
may surprise you.
I prefer to explicitly save the tasks if I'm not await
ing them immediately:
private async Task SendAliveMessageAsync();
public Task KeepaliveTask { get; private set; }
public Client()
{
...
KeepaliveTask = SendAliveMessageAsync();
}
This at least allows the consumers of Client
to detect and recover from exceptions thrown by the SendAliveMessageAsync
method.
On a side note, this pattern is almost equivalent to my "asynchronous initialization" pattern.
回答2:
Edited as previous answer was wrong:
As it's in the constructor, I think you would have to spin up a new thread for it. I would personally do that using
Task.Factory.StartNew(() => SendAliveMessageAsync());
回答3:
Here's an option since you can't call await
from inside a constructor.
I would suggest using Microsoft's Reactive Framework (NuGet "Rx-Main").
The code would look like this:
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();
}
}
This code handles all of the threading required and properly disposes of the StreamWriter
as it goes.
Whenever you want to stop the keep alives just call _subscription.Dispose()
.
回答4:
Since it is a fire and forget operation, you should start it using
SendAliveMessageAsync();
Note that await
does not start a new Task
. It is just syntactical sugar to wait for a Task
to complete.
A new thread is started using Task.Run
.
So inside SendAliveMessageAsync
you should start a new Task:
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++;
}
});
}
回答5:
In your code there is no need to using async/await, just set up a new thread to perform long operation.
private void SendAliveMessage()
{
const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
var sreamWriter = new StreamWriter(_webRequest.GetRequestStream());
while (IsRunning)
{
sreamWriter.WriteLine(keepAliveMessage);
Thread.Sleep(10 * 1000);
}
}
Using Task.Factory.StartNew(SendAliveMessage, TaskCreationOptions.LongRunning)
to perform the operation.
If you really want to using async/await pattern, just call it in constructor without await modifier and the forget it.
public Client()
{
_webRequest = WebRequest.Create("some url");
_webRequest.Method = "POST";
IsRunning = true;
SendAliveMessageAsync(); //just call it and forget it.
}
I think it's not good idea to set up a long running thread or using async/await pattern. Timers maybe more suitable in this situation.