私はC#
s await/async
と現在少し遊んでいます。
私のシナリオでは、WebRequest
プロパティを持つ単純なクライアントオブジェクトがあります。クライアントは、WebRequest
s RequestStream
を介してaliveメッセージを定期的に送信する必要があります。これはクライアントオブジェクトのコンストラクタです:
public Client()
{
_webRequest = WebRequest.Create("some url");
_webRequest.Method = "POST";
IsRunning = true;
// --> how to start the 'async' method (see below)
}
および非同期の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++;
}
}
メソッドはどのように開始すべきですか?
新しいThread(SendAliveMessageAsync).Start();
または
Task.Run(SendAliveMessageAsync); //戻り値の型をTaskに変更します
または
sendAliveMessageAsync();を待ちます。 //コンストラクタが非同期でないため失敗します
私の質問は、await/async
いくつかの点で間違っていると思います。
3番目のオプションは投げています
The 'await' operator can only be used in a method or lambda marked with the 'async' modifier
メソッドはどのように開始すべきですか?
「上記のどれにも」投票します。 :)
「Fire and Forget」は、正しく処理するのが難しいシナリオです。特に、エラー処理には常に問題があります。この場合、 async void
びっくりするかもしれません。
すぐにタスクをawait
ingしない場合は、タスクを明示的に保存することをお勧めします。
private async Task SendAliveMessageAsync();
public Task KeepaliveTask { get; private set; }
public Client()
{
...
KeepaliveTask = SendAliveMessageAsync();
}
これにより、少なくともClient
のコンシューマはSendAliveMessageAsync
メソッドによってスローされた例外を検出して回復できます。
余談ですが、このパターンはmy "非同期初期化"パターン とほぼ同じです。
以前の答えが間違っていたとして編集されました:
それはコンストラクター内にあるので、新しいスレッドを作成する必要があると思います。私は個人的にそれを使ってやります
Task.Factory.StartNew(()=> SendAliveMessageAsync());
fire and forget操作なので、次のコマンドを使用して開始する必要があります
SendAliveMessageAsync();
await
は新しいTask
を開始しないことに注意してください。 Task
が完了するのを待つのは、単なる構文上の砂糖です。Task.Run
を使用して新しいスレッドが開始されます。
したがって、SendAliveMessageAsync
内で新しいタスクを開始する必要があります。
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++;
}
});
}
コンストラクター内からawait
を呼び出すことができないため、ここにオプションがあります。
MicrosoftのReactive Framework(NuGet "Rx-Main")の使用をお勧めします。
コードは次のようになります。
_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();
}
}
_
このコードは、必要なすべてのスレッドを処理し、StreamWriter
を適切に破棄します。
キープアライブを停止したいときはいつでも_subscription.Dispose()
を呼び出すだけです。
コードでasync/awaitを使用する必要はありません。新しいスレッドをセットアップして長い操作を実行するだけです。
_private void SendAliveMessage()
{
const string keepAliveMessage = "{\"message\": {\"type\": \"keepalive\"}}";
var sreamWriter = new StreamWriter(_webRequest.GetRequestStream());
while (IsRunning)
{
sreamWriter.WriteLine(keepAliveMessage);
Thread.Sleep(10 * 1000);
}
}
_
Task.Factory.StartNew(SendAliveMessage, TaskCreationOptions.LongRunning)
を使用して操作を実行します。
本当にasync/awaitパターンを使いたい場合は、await修飾子なしでコンストラクターでそれを呼び出し、それを忘れてください。
_public Client()
{
_webRequest = WebRequest.Create("some url");
_webRequest.Method = "POST";
IsRunning = true;
SendAliveMessageAsync(); //just call it and forget it.
}
_
実行時間の長いスレッドを設定したり、非同期/待機パターンを使用したりすることはお勧めしません。この状況では、タイマーの方が適している可能性があります。