Search code examples
c#asynchronous.net-coreasync-awaitjson-rpc

Console.Error.WriteLineAsync() vs Console.WriteLine, what is the difference. Why does the later throw exception?


I am trying to understand StreamJsonRpc and running an example from here.

I have created a tone down example here. It contains server and client and communicates via StreamJsonRpc.

In the server, when I use Console.WriteLine, it throws exception. When I revert back to using await Console.Error.WriteLineAsync(), it runs fine.

Exception is throw

Why is that? Looks like there is something to do with await async that I am missing.

The following method works.

private static async Task RespondToRpcRequestsUsingConsoleErrorAsync(Stream stream, int clientId)
{
    await Console.Error.WriteLineAsync($"Connection request #{clientId} received. Spinning off an async Task to cater to requests.");
    var jsonRpc = JsonRpc.Attach(stream, new Server());
    await Console.Error.WriteLineAsync($"JSON-RPC listener attached to #{clientId}. Waiting for requests...");
    await jsonRpc.Completion;
    await Console.Error.WriteLineAsync($"Connection #{clientId} terminated.");
}

The following throws exception. You can see in the following Console.WriteLine() replacing await Console.Error.WriteLineAsync() above.

private static async Task RespondToRpcRequestsUsingConsoleAsync(Stream stream, int clientId)
{
    Console.WriteLine($"Connection request #{clientId} received. Spinning off an async Task to cater to requests.");
    var jsonRpc = JsonRpc.Attach(stream, new Server());
    Console.WriteLine($"JSON-RPC listener attached to #{clientId}. Waiting for requests...");
    await jsonRpc.Completion;
    Console.WriteLine($"Connection #{clientId} terminated.");
}

I tried using jsonRpc.Completion.Wait(); and jsonRpc.Completion.GetAwaiter().GetResult(); as follows, but no success.

private static void RespondToRpcRequestsUsingConsoleWithWait(Stream stream, int clientId)
{
    Console.WriteLine($"Connection request #{clientId} received. Spinning off an async Task to cater to requests.");
    var jsonRpc = JsonRpc.Attach(stream, new Server());
    Console.WriteLine($"JSON-RPC listener attached to #{clientId}. Waiting for requests...");
    jsonRpc.Completion.Wait(); // SEE HERE
    Console.WriteLine($"Connection #{clientId} terminated.");
}

private static void RespondToRpcRequestsUsingConsoleWithAwaiterAndResult(Stream stream, int clientId)
{
    Console.WriteLine($"Connection request #{clientId} received. Spinning off an async Task to cater to requests.");
    var jsonRpc = JsonRpc.Attach(stream, new Server());
    Console.WriteLine($"JSON-RPC listener attached to #{clientId}. Waiting for requests...");
    jsonRpc.Completion.GetAwaiter().GetResult(); // SEE HERE
    Console.WriteLine($"Connection #{clientId} terminated.");
}

How to run the example.

  1. Just open this in Visual Studio, set the client as start up project and press F5.
  2. With command line, cd into the client project and run it.
cd JsonRpcStdIoClient
dotnet run --project ./JsonRpcStdIoClient.csproj

Solution

  • This has nothing to do with async. You are using StandardInput/StandardOutput to communicate between processes, Console.WriteLine writes to StandardOutput. So when you call it on the server it will send some random, at least from the JsonRpc standpoint of view, data to the communication channel which it seems that JsonRpc can't process, hence the result. Console.Error on the other hand writes to standard error stream, so it does not affect the communication channel.

    As experiment remove all Console.WriteLine in the server and for the client add monitoring of the duplex stream:

    // setup process and stdioStream
    
    var monitoringStream = new MonitoringStream(stdioStream);
    var inwr = new MemoryStream();
    var outwr = new MemoryStream();
    monitoringStream.DidRead += (s, e) => inwr.Write(e);  
    monitoringStream.DidWrite += (s, e) => outwr.Write(e);
    
    try
    {
        await ActAsRpcClientAsync(monitoringStream);
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
    
    Console.WriteLine("----\nIn:\n");
    Console.WriteLine(Encoding.UTF8.GetString(inwr.ToArray()));
    Console.WriteLine("----\nOut:\n");
    Console.WriteLine(Encoding.UTF8.GetString(outwr.ToArray()));
    

    Which results in something like

    Connected. Sending request...
    Received request: 3 + 5
    3 + 5 = 8
    ----
    In:
    
    Content-Length: 35
    
    {"jsonrpc":"2.0","id":2,"result":8}
    ----
    Out:
    
    Content-Length: 54
    
    {"jsonrpc":"2.0","id":2,"method":"Add","params":[3,5]}