-
-
Save AArnott/0d5f4645ad7e9a765cee to your computer and use it in GitHub Desktop.
| <Project Sdk="Microsoft.NET.Sdk"> | |
| <PropertyGroup> | |
| <OutputType>exe</OutputType> | |
| <TargetFrameworks>net472;net5.0-windows</TargetFrameworks> | |
| </PropertyGroup> | |
| <ItemGroup> | |
| <PackageReference Include="System.IO.Pipes.AccessControl" Version="5.0.0" /> | |
| </ItemGroup> | |
| </Project> |
| using System; | |
| using System.IO; | |
| using System.IO.Pipes; | |
| using System.Threading.Tasks; | |
| class Program | |
| { | |
| static void Main(string[] args) | |
| { | |
| const string pipeName = @"testpipe"; | |
| Task serverTask = RunServerAsync(pipeName); | |
| Task clientTask = RunClientAsync(pipeName); | |
| Task.WaitAll(serverTask, clientTask); | |
| } | |
| private static async Task RunServerAsync(string pipeName) | |
| { | |
| PipeSecurity security = new PipeSecurity(); | |
| security.AddAccessRule(new PipeAccessRule($"{Environment.UserDomainName}\\{Environment.UserName}", PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow)); | |
| #if NET5_0 // .NET Core 3.1 does not support setting ACLs on named pipes. | |
| NamedPipeServerStream serverPipe = NamedPipeServerStreamAcl.Create(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 4096, 4096, security); | |
| #elif NETFRAMEWORK | |
| NamedPipeServerStream serverPipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous); | |
| #endif | |
| await serverPipe.WaitForConnectionAsync(); | |
| var writer = new StreamWriter(serverPipe); | |
| writer.AutoFlush = true; | |
| var reader = new StreamReader(serverPipe); | |
| await writer.WriteLineAsync("HELLO"); | |
| do | |
| { | |
| string line = await reader.ReadLineAsync(); | |
| if (line == "BYE" || line == null) | |
| { | |
| break; | |
| } | |
| await writer.WriteLineAsync(line); | |
| } while (true); | |
| serverPipe.Disconnect(); | |
| } | |
| private static async Task RunClientAsync(string pipeName) | |
| { | |
| var clientPipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); | |
| await clientPipe.ConnectAsync(); | |
| var writer = new StreamWriter(clientPipe); | |
| writer.AutoFlush = true; | |
| var reader = new StreamReader(clientPipe); | |
| string line = await reader.ReadLineAsync(); | |
| if (line != "HELLO") | |
| { | |
| throw new ApplicationException("Error"); | |
| } | |
| await writer.WriteLineAsync("1+1=2"); | |
| line = await reader.ReadLineAsync(); | |
| if (line != "1+1=2") | |
| { | |
| throw new ApplicationException("Error"); | |
| } | |
| await writer.WriteLineAsync("BYE"); | |
| clientPipe.WaitForPipeDrain(); | |
| clientPipe.Close(); | |
| } | |
| } |
Also, if you want a "fully-async" implementation, you could replace clientPipe.Connect() with:
var connectAction = new Action(clientPipe.Connect);
await Task.Factory.StartNew(connectAction);or something similar
Nice catch on use of Connect(), @puntopaz.
Using Task.Factory.StartNew around a synchronous function doesn't actually make it purely async. It just moves the synchronous work to another thread, but still ties up a thread. Using clientPipe.ConnectAsync is preferable since it truly releases all threads while waiting for connection to proceed (or at least, that's how async I/O is expected to behave).
So, I run this from a console application, and it works. Then I try running it from within a WPF application, and both functions get permanently stuck: one in await clientPipe.ConnectAsync(); the other in await serverPipe.WaitForConnectionAsync();. Does anyone have any idea as to what might be wrong? The code is exactly the same, only I had to rename Main() to Test(). I even tried replacing pipeName = @"testpipe" with pipeName = Guid.NewGuid().ToString(); no difference. It also gets stuck when I run two different instances of the WPF application, where one only runs the server and the other only runs the client.
@mikenakis: Your WPF app has a SynchronizationContext that my console app doesn't have. It's purpose is to keep async methods on the main thread that start on the main thread. But when you combine that with a Task.Wait() call (or in this case, Task.WaitAll) it'll deadlock because you're blocking the main thread that an async method needs to get back to. Make this change to fix it:
- static void Test(string[] args)
+ static async Task Test(string[] args)
{
const string pipeName = @"testpipe";
Task serverTask = RunServerAsync(pipeName);
Task clientTask = RunClientAsync(pipeName);
- Task.WaitAll(serverTask, clientTask);
+ await Task.WhenAll(serverTask, clientTask);
}I suspect it does not work under WPF because somewhere in the deep internals of the async mechanism they detect that there is a dispatcher available, and they try to make use of it, which of course miserably fails because it does not take into account the fact that we invoke Task.WaitAll(); -- Can anyone confirm my suspicion?
@AArnott thanks for the answer. I had not seen it while I was typing mine.
@AArnott yes, that was it. Thanks!
On my pc, it generates a "Pipe is broken" exception on the instruction:
clientPipe.WaitForPipeDrain()probably because the instruction:
serverPipe.Disconnect()has already been executed.
I had to surround it with a try-except to get it working properly: