Search code examples
asynchronouswindows-runtimetaskblockasync-await

WinRT: blocking Task


I'm implementing network layer of my application, that is using an asynchronous JSON-RPC protocol.

In order to communicate with the server, i'd like to make a method that will send a proper request, wait until the server sends response, and return it. Everything with use of async/await keywords.

Here's simplified sample code:

string response;

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

The main problem with this pattern it this blocking action. This action should block current Task, and allow to handle timeout. I've tried to use ManualResetEvent, and WaitOne(int) to handle this, but it freezes whole Thread, and because I'm using async/await only, it freezes whole application (UI Thread to me more precise).

The solution, that looks quite hacky for me is that I can use Task.Delay with CancellationTokens.

It looks like this:

...
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();      
}

The problem with that solution (besides it looks like a hack) is performance - every request there is an excetion thrown, that needs to be handled (skipped in that case). This one is slow, and it hurts :)

How can I do it in more elegant way? Is there any other option to block a Task?


Solution

  • Convert the receive and send loops into one loop that is trigged by the send button:

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

    Not sure if you even need the loop then because I don't know your exact requirements. It could look like this:

    await sendRequest();
    await receiveResponse();
    

    That would execute a single request-response-cycle.


    Reading your comment below I want to add the following: You're right, now you need both loops. I'd tackle this by first creating a class to represent a request that is in flight:

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

    When sending something, you create an instance of this class and add it to a Dictionary<int, Request>. The ID is a shared identifier that the server can use to respond to your request (I understand multiple requests can be outstanding).

    When receiving a response you save the result and mark the CompletedTask as completed. Sour send method should look like:

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

    The TaskCompletionSource will act as an event. The Request class encapsulates all related data.