5

次のデータグラム ソケット操作を TPL でラップして API をクリーンアップし、クラスと同じようにasyncand とうまく動作するようにしたいと思います。awaitStreamSocket

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;
}

難点は、クラスをイベント非同期パターンと新しいパターンの奇妙な寄せ集めにMessageReceived変えるイベントです。とにかく、後者に適合するようにハンドラーを適応させることができるので、それほど怖くありません。DatagramSocketasyncTaskCompletionSource<T>

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

この操作を適切にラップしてタイムアウトとキャンセルを組み込む正しい方法は何ですか? 後者の引数を取るためにこの関数を拡張したいのですCancellationTokenが、どうすればよいでしょうか? 私が思いついた唯一のことはTask.Delay、次の行に沿ってこれら 2 つの動作をサポートするために、タイムアウト値とキャンセル トークンを渡す追加の「監視」タスクを作成することです。

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;
}

MessageReceivedただし、これには、遅延タスクとハンドラーの間の潜在的な競合状態を含む、あらゆる種類の問題があります。このアプローチを確実に機能させることはできませんでした。さらに、スレッドプールの非効率的な使用と同様に、とてつもなく複雑に思えます。手間がかかり、エラーが発生しやすく、頭が痛くなります。

補足:DatagramSocket一般的に API に混乱しているのは私だけですか? IAsyncActionWinRT モデルと TPL にトリッキーな EAP が組み込まれた見苦しい集合体のように見えるだけでなく、名前が付けられたメソッドを含む UDP など、基本的にコネクションレスのプロトコルを表すことを意図した API にあまり満足していませんConnectAsync。これは私には矛盾しているように思えます。

4

2 に答える 2

2

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

名前が 100% 意味を成さConnectないことに同意しますが、特に. そして、システムがドメイン名を解決してソケットにポートを割り当てることができるように、このようなメソッドが必要です。StreamSocket

あなたの方法については、データグラムを受信するための別の方法を作成する必要があるという @usr に同意します。そして、ある非同期モデルを別のモデルに変換したい場合、元のモデルがネイティブにサポートしていない機能を追加しながら、面倒なことになるでしょう。それについてできることは何もないと思います。

適切に実装すれば、非効率になることもありません。Taskが完了した後、MessageReceivedイベントのサブスクライブが解除され、 に関連付けられたタイマーが破棄Delay()され ( に渡したトークンをキャンセルすることでこれを行いますDelay())、デリゲートが破棄されることを確認する必要があります。渡されたもので登録されたものは登録されていません((ab)usingの代わりに直接CancellationToken使用する必要があると思います)。Register()Delay()

競合状態については、もちろん考えなければなりません。しかし、ここでそれを処理する比較的簡単な方法があります: (eg ) のTryメソッドを使用します。TaskCompletionSourceTrySetResult()

于 2013-02-08T12:54:21.710 に答える
0

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

タイマーロジックを再利用可能なヘルパーメソッドに移動することをお勧めします。これにより、コードが無関係なものがたくさん混ざったスパゲッティのように見えるのを防ぎます。

于 2013-02-08T11:32:50.477 に答える