I have a simple .NET6 WinForms app that creates a system tray icon with one menu item: Exit. The app spawns an async task to manage a named pipe that receives and logs messages from a Windows Service. If the user clicks the Exit menu item, the event handler cancels the token, however the named pipe WaitForConnectionsAsync
call doesn't recognize the cancellation.
I have tried many variations on the startup sequence. In this case I thought I might need configureAwait(false)
for the named pipe thread, but it still doesn't work. Scattering around debug messages shows that the UI portion does properly exit and the token is canceled, but the named pipe class never exits the WaitForConnectionAsync
call.
Program.cs
internal static class Program
{
private static SystemTrayApp trayApp = null;
private static CancellationTokenSource ctsMessageServer = new();
[STAThread]
static void Main()
=> MainAsync().GetAwaiter().GetResult();
static async Task MainAsync()
{
List<Task> tasks = new();
tasks.Add(Task.Run(() =>
{
ApplicationConfiguration.Initialize();
trayApp = new SystemTrayApp();
Application.Run(trayApp);
}));
tasks.Add(Task.Run(async Task () =>
{
await MessageServer.RunServer(ctsMessageServer.Token).configureAwait(false);
}));
await Task.WhenAll(tasks);
Environment.Exit(0);
}
public static void Exit()
{
ctsMessageServer.Cancel();
trayApp.Dispose();
Application.Exit();
}
}
MessageServer.cs (partial)
internal static class MessageServer
{
private static readonly string pipeServerName = "fubar";
private static readonly string separatorControlCode = "\u0014";
public static async Task RunServer(CancellationToken cancellationToken)
{
try
{
while(!cancellationToken.IsCancellationRequested)
{
using var server = new NamedPipeServerStream(pipeServerName, PipeDirection.In);
await server.WaitForConnectionAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
// omitted the rest, never reaches the line above
// below: reading the data, disconnect, catch blocks, etc.
Nothing interesting in SystemTrayApp
-- it just creates the icon and menu, and the Exit event-handler disposes some internal objects, then calls Program.Exit
shown above.
For the MessageServer
class, I know all of that code is correct and works, I've used it in many other projects (example), and I've used async/await and cancellation tokens in many scenarios over the years, including complex multi-threaded WPF apps. I assume something is different with WinForms in the picture...
According to the Remarks on WaitForConnectionAsync:
Cancellation requests using the cancellation token will only work if the NamedPipeServerStream object was created with a pipe option value of PipeOptions.Asynchronous or if the cancellation occurs before the WaitForConnectionAsync method is called.
Try this:
using var server = new NamedPipeServerStream("", PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With