3

I'm communicating with another process via named pipes. The pipe server is implemented in C# and the client is written in C. The server is a WPF application.

I need to create a NamedPipeServerStream and wait (synchronously) up to 1 second for the client to connect. And then I need to know whether the client connected.

As NamedPipeServerStream's only way to cancel/timeout a wait for the client to connect is via its asynchronous WaitForConnectionAsync method - which takes a CancellationToken - I've implemented what I believe is a synchronous wait like so:

public bool WaitOneSecondForClientConnect()
{
    bool result = false;
    try
    {
        result = WaitForConnectionAsyncSyncWrapper().Result;
    }
    catch (AggregateException e)
    {
        log.Write("Error waiting for pipe client connect: " + e.InnerException.Message);
    }
    return result;
}

private async Task<bool> WaitForConnectionAsyncSyncWrapper()
{
    CancellationTokenSource cts = new CancellationTokenSource(1000);
    await pipe.WaitForConnectionAsync(cts.Token);
    return pipe.IsConnected;
}

The pipe is defined like so: NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1, 1); The WaitOneSecondForClientConnect() function runs on the UI thread.

What should make it synchronous is accessing the async WaitForConnectionAsyncSyncWrapper() function's Result property on the Task<bool> it returns. In order to access the Result, the async function must have fully returned, and it can't execute the return pipe.IsConnected line until the function is resumed after await pipe.WaitForConnectionAsync(cts.Token); has completed. At least that's my understanding.

So the problem: although the client program says it's opened my server's pipe (which has already been created of course before the code above executes), WaitOneSecondForClientConnect() never returns. If I break into the server, it's on this line: result = WaitForConnectionAsyncSyncWrapper().Result;.

So I guess it's waiting for the Task's Result to be available, which should be the value of pipe.IsConnected if the client connected within 1 second, or it should throw an AggregateException when I access it if the await has completed because the token has been cancelled (after 1 second). But it's just hanging completely.

On the other hand, if I cancel the token before it starts e.g. by putting a Thread.Sleep(2000); right before calling await pipe.WaitForConnectionAsync(cts.Token);, then the connection is cancelled successfully (I think it doesn't even try to start because the token is already cancelled) - accessing the Result property throws an AggregateException, etc...

A few things to note.

  • If I replace the content of WaitOneSecondForClientConnect() with a standard synchronous pipe.WaitForConnection();, it works every time - i.e. the client connects, and the function returns.
  • In a test program I wrote to initially get this async/sync stuff working, connecting in this synchronous-asynchronous way works every time. It's a console program as opposed to my real program which is WPF. The relevant code of the test program is listed below.
  • The code posted above has actually worked a couple of times, and failed maybe 30-40 times.
  • If my client doesn't open my pipe, my "real" code still hangs, whereas my test code waits the specified time period and then prints "Connection failed." as expected (see below) - which is exactly the behaviour that should be happening in my real code.

The test code that works:

var pipe = new NamedPipeServerStream("SemiUsefulPipe_" + pid.ToString() + "ctest", PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1, 1);

// ... dll containing pipe client is injected in client process at this point.

try
{
    var result = ConnectAsync(pipe).Result;
}
catch (AggregateException)
{
    Console.WriteLine("Connection failed.");
}

...

private static async Task<bool> ConnectAsync(NamedPipeServerStream pipe)
{
    CancellationTokenSource cts = new CancellationTokenSource(1000);
    await pipe.WaitForConnectionAsync(cts.Token);
    return pipe.IsConnected;
}
404
  • 8,022
  • 2
  • 27
  • 47

1 Answers1

2

You can't mix async and non-async methods like this. What's happening is that your WaitOneSecondForClientConnect method is waiting for WaitForConnectionAsyncSyncWrapper method to complete. But that guy needs its calling thread to be free so it can rehydrate the original context. So you've just created a deadlock. For details, see https://msdn.microsoft.com/en-us/magazine/jj991977.aspx.

Instead, you need to go async all the way down.

public async Task<bool> WaitOneSecondForClientConnect()
{
    bool result = false;
    try
    {
        result = await WaitForConnectionAsyncSyncWrapper();
    }
    catch (Exception e)
    {
        log.Write("Error waiting for pipe client connect: " + e.Message);
    }
    return result;
}
howcheng
  • 2,211
  • 2
  • 17
  • 24
  • Brilliant, that link describes my problem to a T (including why it works in my test code and not my GUI app). Now there's almost certainly a better or "more correct" way of doing this, as I'm new to async/await; so in order to make the method synchronous I am leaving my code as is, but preventing the deadlock by adding `ConfigureAwait(false)` to the end of `await pipe.WaitForConnectionAsync(cts.Token)` so it executes the remainder on the thread pool, and it works! – 404 Sep 11 '15 at 15:59