1

非同期 JSON-RPC プロトコルを使用するアプリケーションのネットワーク層を実装しています。

サーバーと通信するために、適切なリクエストを送信し、サーバーが応答を送信するまで待って、それを返すメソッドを作成したいと思います。async/await キーワードを使用するすべて。

簡単なサンプル コードを次に示します。

文字列応答;

Task<string> SendRequest(string methodName, string methodParams)
{
    string request = generateRequest(methodName, methodParams);

    await Send(request); // this will send using DataWriter, and StreamSocket

    // block Task until response arrives

    return response;
}


async void ReceiveLoop()
{
    while (true)
    {
            uint numStrBytes = await _reader.LoadAsync(BufferSize);

            string msg = _reader.ReadString(numStrBytes);

            response = msg;

            // unblock previously blocked SendRequest
        }        
    }
}

async void main()
{
    RecieveLoop();  
}

async void SendButtonPressed()
{
    string response = await SendRequest("Test method", "Test params");

    Debug.WriteLine("Response = " + response);
}

このパターンの主な問題は、このブロッキング アクションです。このアクションは、現在のタスクをブロックし、タイムアウトを処理できるようにする必要があります。これを処理するために ManualResetEvent と WaitOne(int) を使用しようとしましたが、スレッド全体がフリーズし、async/await のみを使用しているため、アプリケーション全体がフリーズします (より正確には UI スレッド)。

私にとって非常にハックに見える解決策は、CancellationTokens で Task.Delay を使用できることです。

次のようになります。

...
CancellationTokenSource cts;
int timeout = 10000;

Task<string> SendRequest(string methodName, string methodParams)
{
    ... (prepare request, and send)

    cts = new CancellationTokenSource();

    try
    {
        await Task.Delay(timeout, cts.Token);
    } catch(TaskCanceledException)
    {

    }

    // do rest
}

async void ReceiveLoop()
{
    // init recieve loop, and recieve message

    cts.Cancel();      
}

そのソリューションの問題(ハックのように見えることに加えて)はパフォーマンスです-すべてのリクエストで例外がスローされ、処理する必要があります(その場合はスキップされます)。これは遅くて痛いです:)

どうすればよりエレガントな方法でそれを行うことができますか? タスクをブロックする他のオプションはありますか?

4

2 に答える 2

1

受信ループと送信ループを、送信ボタンによってトリガーされる 1 つのループに変換します。

while(!cancel) {
    await sendRequest();
    await receiveResponse();
}

正確な要件がわからないため、ループが必要かどうかはわかりません。次のようになります。

await sendRequest();
await receiveResponse();

それは単一の要求応答サイクルを実行します。


以下のコメントを読んで、次を追加したいと思います。そうです、今は両方のループが必要です。最初に、処理中のリクエストを表すクラスを作成することで、これに取り組みます。

class Request { int ID; object Response; TaskCompletionCource CompletedTask; }

何かを送信するときは、このクラスのインスタンスを作成し、Dictionary<int, Request>. はID、サーバーがリクエストに応答するために使用できる共有識別子です (複数のリクエストが未処理である可能性があることは理解しています)。

応答を受信したら、結果を保存し、CompletedTask完了としてマークします。Sour send メソッドは次のようになります。

var request = CreateRequest();
await sendRequest(request);
await request.CompletedTask.Task;
return request.Response;

TaskCompletionSourceイベントとして機能します。このRequestクラスは、関連するすべてのデータをカプセル化します。

于 2012-11-26T12:24:52.943 に答える
0

Ok、

usr の助けを借りて、現在のタスクをタイムアウト付きでブロックするこのコードを書くことができました。

TaskTimeout 拡張機能:

internal struct VoidTypeStruct{}

 public static class TaskTimeoutExtension
    {
        public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
        {
            if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
                await task;
            else
                throw new TimeoutException();
        }
    }

タスクのブロック:

var tcs = new TaskCompletionSource<VoidTypeStruct>();

try
{
    await Task.Run(async () => { await tcs.Task; }).TimeoutAfter(RequestTimeout);
}
catch (TimeoutException)
{                           
}

// store tcs variable somewhere

タスクのブロック解除:

tcs.SetResult(new VoidTypeStruct());

これは非常に高速に機能します (少なくとも以前のソリューションよりもはるかに優れています)。

最後の質問: 本当に、タスクをブロックする他の方法はありませんか? クリティカル セクションについてはどうですか。タスク用のミューテックスはありませんか?

于 2012-11-26T22:35:59.880 に答える