5

StreamSocketのように、asyncawaitでうまく動作するように、APIをクリーンアップするためにTPLで以下のデータグラムソケット操作をラップしたいと思います。async/awaitで使用するためにDatagramSocket.MessageReceivedをどのように適合させますか?

public static async Task<bool> TestAsync(HostName hostName, string serviceName, byte[] data) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 
    var socket = new DatagramSocket(); 
    socket.MessageReceived += (sender, e) => 
    { 
     var status = false; // Status value somehow derived from e etc. 
     tcs.SetResult(status); 
    }; 
    await socket.ConnectAsync(hostName, serviceName); 
    var stream = await socket.GetOutputStreamAsync(); 
    var writer = new DataWriter(stream); 
    writer.WriteBytes(data); 
    await writer.StoreAsync(); 
    return tcs.Task; 
} 

付着点はイベントの非同期パターンの奇妙寄せ集めと新しいasyncパターンにDatagramSocketクラスをオンMessageReceivedイベントです。とにかく、TaskCompletionSource<T>は私が後者に従うようにハンドラを適応させることができるので、あまり恐ろしいことではありません。

エンドポイントが決してデータを返さない限り、これはかなりうまくいくようです。 MessageReceivedハンドラに関連付けられたタスクは完了しないため、TestAsyncから返されたタスクは完了しません。

この操作をうまくラップしてタイムアウトとキャンセルを組み込む正しい方法は何ですか?この関数を拡張して、後者の引数をCancellationTokenとしたいのですが、どうすればいいですか?しかし、

public static async Task<bool> CancellableTimeoutableTestAsync(HostName hostName, string serviceName, byte[] data, CancellationToken userToken, int timeout) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 
    var socket = new DatagramSocket(); 
    socket.MessageReceived += (sender, e) => 
    { 
     var status = false; // Status value somehow derived from e etc. 
     tcs.SetResult(status); 
    }; 
    await socket.ConnectAsync(hostName, serviceName); 
    var stream = await socket.GetOutputStreamAsync(); 
    var writer = new DataWriter(stream); 
    writer.WriteBytes(data); 
    await writer.StoreAsync(); 

    var delayTask = Task.Delay(timeout, userToken); 
    var t1 = delayTask.ContinueWith(t => { /* Do something to tcs to indicate timeout */ }, TaskContinuationOptions.OnlyOnRanToCompletion); 
    var t2 = delayTask.ContinueWith(t => { tcs.SetCanceled(); }, TaskContinuationOptions.OnlyOnCanceled); 

    return tcs.Task; 
} 

:私が作ってみただけの事は、私は次の行に沿って、これらの2つの動作をサポートするためのタイムアウト値とキャンセルトークンを渡すためにTask.Delayを使用して「監視」の追加タスクを作成することですこれには、遅延タスクとMessageReceivedハンドラの間の潜在的な競合状態を含むあらゆる種類の問題があります。私はこのアプローチを確実に動作させることはできませんでしたが、スレッドプールの非効率的な使用だけでなく、非常に複雑に思えます。間違っていて誤りがちで頭が痛い。

サイドノート:私は、一般的にDatagramSocket APIで混乱している人だけですか?だけでなく、それは投入されたいくつかのトリッキーなEAPとIAsyncAction WinRTのモデルとTPLの醜い寄せ集めのように見えるん、私はそのようなUDPがでConnectAsyncという名前のメソッドを含むように基本的にコネクションレスのプロトコルを表すことが意図されているAPIとひどく慣れていませんよそれら。これは私にとっては矛盾しているようです。

+0

2番目のメソッド全体の代わりに、最初のタスクをTask.Delayと組み合わせてTask.WhenAnyを使用する新しいメソッドを作成します。あなたがあなたの仕事を完了するか、タイムアウトが発生した場合は、それを返します。 –

答えて

2

まず、私はDatagramSocketのインタフェースがこそUDPの性質上、理にかなっていると思います。データグラムのストリームがある場合は、それを表す適切な方法です。 WinRT IAsyncAction(または.Net Task)は、各データを明示的に要求するプルモデルのみを表すことができます(たとえば、ReadNextDatagramAsync()のメソッドがあります)。これはTCPには意味があります。なぜなら、フロー制御を持っているからです。データをゆっくりと読むと、送信側も遅く送信します。しかし、UDPの場合、プッシュモデル(WinRTと.Netのイベントで表されます)ははるかに意味があります。

は、と私は名前Connectが100%の意味がないことに同意し、私はそれがほとんどん特にStreamSocketとより一貫性を保つために、理にかなっていると思います。このような方法が必要なので、システムはドメイン名を解決してソケットにソケットを割り当てることができます。

あなたの方法では、私はデータグラムを受信するための別の方法を作成する必要があることに同意します。そして、ある非同期モデルを別のモデルに変換したいのであれば、元のモデルがネイティブにサポートしていない機能を追加している間に、それは慎重になるだろう、私はそれについて何もできないと思う。あなたはそれを正しく実装する場合

それはまた、非効率的ではありません:あなたはTaskが完了した後、MessageReceivedイベントが解除されることを確認する必要があり、Delay()に関連付けられたタイマーが配置されている(あなたがやることにトークンをキャンセルすることにより、あなたはDelay()に渡されました)、渡されたCancellationTokenで登録された代理人は登録解除されています(私はあなたのためにを使って(ab)の代わりにRegister()を直接使うべきだと思います)。

競争条件に関しては、もちろんそれらについて考える必要があります。しかし、それに対処する比較的簡単な方法があります:TaskCompletionSourceTryメソッド(例:TrySetResult())を使用してください。

0

タイムアウト:タイマーを開始し、tcs.TrySetCancelled()を使用してタスクを完了します。キャンセルの場合は、cancellationToken.Registerを使用して、キャンセルするように設定したコールバックを登録します。必ずタイマーを廃棄してください。

タイマロジックを再利用可能なヘルパーメソッドに移動することをお勧めします。これは、関連性のないものが混在しているスパゲッティのように見えないようにします。

関連する問題