-
-
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(); | |
} | |
} |
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!
Also, if you want a "fully-async" implementation, you could replace
clientPipe.Connect()
with:or something similar