Search code examples
c#.netnamed-pipes

Asynchronous operations on a System.IO.Pipes named pipe


I have implemented a simple NamedPipeServerStream / NamedPipeClientStream solution, where I want the client to be able to send requests, but also receive responses, and I never know which order they will appear - request sending is event triggered outside the client's control, and response receiving is triggered by the server sending a response. The server needs to be able to receive requests, act upon them, and then send a response, asynchronously, without the "act upon them" blocking further incoming requests.

In the client, I am starting a task that reads from the stream continuously, and then when an event occurs, I send a request in another task. However, as long as I'm reading from the stream, I do not seem to be allowed to write. How to listen for incoming server responses asynchronously from sending requests?

I made a sample application to demonstrate this. The behavior of this application is that the server starts and stands waiting for requests but never receives any. And the client starts reading, attempts to write a request to the stream but hangs on FlushAsync.

It feels like the client's ReadLineAsync call needs to be paused or release the stream somehow, but how do I do that? And what will happen to any server responses that might appear in the meantime?

using System.IO.Pipes;

namespace NamedPipeThreadTest
{
    internal class StackOverflowExample
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello! You are the client. Press any key to send a request.");

            Task[] tasks = new Task[]
            {
                Task.Run(() => Server()),
                Task.Run(() => Client())
            };

            Task.WaitAll(tasks);
        }

        static async Task Server()
        {
            using (var pipeServer = new NamedPipeServerStream("mypipe", PipeDirection.InOut))
            {
                pipeServer.WaitForConnection();

                using (var reader = new StreamReader(pipeServer))
                using (var writer = new StreamWriter(pipeServer))
                {
                    while (true)
                    {
                        string? line = await reader.ReadLineAsync();

                        // I want this to run in the background and continue execution without waiting for it.
                        Task dummyTask = Task.Run(() => DummyTask(writer));
                    }
                }
            }
        }

        static async Task DummyTask(StreamWriter writer)
        {
            // This simulates some kind of work being done.
            Task.WaitAll(Task.Delay(3000));

            string? line = $"Performed dummy task as requested.";
            await writer.WriteLineAsync(line);
            await writer.FlushAsync();
        }

        static async Task Client()
        {
            using (var pipeClient = new NamedPipeClientStream(".", "mypipe", PipeDirection.InOut))
            {
                pipeClient.Connect();

                using (var reader = new StreamReader(pipeClient))
                using (var writer = new StreamWriter(pipeClient))
                {
                    // I want this to run in the background and continue execution without waiting for it.
                    Task readResultTask = Task.Run(() => ReadDummyTaskResult(reader));

                    while (true)
                    {
                        Console.ReadKey(true);
                        Console.WriteLine("Received key press, sending request.");
                        string? line = "Perform dummy task, please!";
                        await writer.WriteLineAsync(line);
                        await writer.FlushAsync();
                        Console.WriteLine("Sent request.");
                    }
                }
            }
        }

        static async Task ReadDummyTaskResult(StreamReader reader)
        {
            while (true)
            {
                string? line = await reader.ReadLineAsync();
                // Propagate result further into the client, then read the next result.
            }
        }
    }
}


Solution

  • I can't say I understand why, but opening the client/server streams as asynchronous appears to fix the issue:

    var pipeServer = new NamedPipeServerStream("mypipe", PipeDirection.InOut, -1, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
    
    var pipeClient = new NamedPipeClientStream(".", "mypipe", PipeDirection.InOut, PipeOptions.Asynchronous);