I am trying to create a service with WPF where the service will communicate with WPF telling the WPF to display icon tray and ballontip.
I have designed the following codes:
App.xaml.cs
namespace Service_UI
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public LogWriter logwriter = new LogWriter();
System.Windows.Forms.NotifyIcon nIcon = new System.Windows.Forms.NotifyIcon();
Server IPCS = new Server();
protected override void OnStartup(StartupEventArgs e)
{
nIcon.Icon = new Icon($@"{AppDomain.CurrentDomain.BaseDirectory}/app.ico");
nIcon.Text = "Service";
Task.Run(async () => await ServerMessages());
MainWindow mainWindow = new MainWindow();
mainWindow.Hide(); // Start hidden
}
private async Task ServerMessages()
{
await IPCS.Connect();
while (true)
{
string serverMessage = await IPCS.ReadMessages();
if (serverMessage == "Service_Start")
{
Dispatcher.Invoke(() =>
{
nIcon.Visible = true;
nIcon.ShowBalloonTip(5000, "Service UI", "UI will open up Shortly", System.Windows.Forms.ToolTipIcon.Info);
nIcon.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
nIcon.ContextMenuStrip.Items.Add("Settings", Image.FromFile(@"Resources\cogs_icon.ico"), OnSettingsClicked);
nIcon.ContextMenuStrip.Items.Add("Exit", Image.FromFile(@"Resources\exit_icon.ico"), OnExitClicked);
// This section is about selecting the View MainWindow and running the application:
MainWindow = new MainWindow();
MainWindow.Show();
logwriter.LogWrite("Application is running now", MessageLevels.Info);
//base.OnStartup(e);
});
}
else if (serverMessage == "Service_Stop")
{
Dispatcher.Invoke(() =>
{
nIcon.Dispose();
//base.OnExit(e);
IPCS.Dispose();
//base.OnExit(e);
MainWindow.Hide();
});
}
}
}
private void OnExitClicked(object sender, EventArgs e)
{
nIcon.Dispose();
MainWindow.Hide();
logwriter.LogWrite("Application has been minimised", MessageLevels.Info);
}
private void OnSettingsClicked(object sender, EventArgs e)
{
MainWindow.Show();
logwriter.LogWrite("Application is displayed", MessageLevels.Info);
}
}
}
Service file
namespace MainService
{
public partial class MainService : ServiceBase
{
Timer tmr = new Timer();
LogWriter logWriter = new LogWriter();
Client IPCC = new Client();
public MainService()
{
InitializeComponent();
this.ServiceName = "MainService";
}
protected override void OnStart(string[] args)
{
logWriter.LogWrite($"Service initiated on {DateTime.Now}", MessageLevels.Info);
tmr.Elapsed += new ElapsedEventHandler(OnElapsedTime);
tmr.Interval = 5000;
tmr.Enabled = true;
logWriter.LogWrite($"Service will initiate the WPF on {DateTime.Now}", MessageLevels.Info);
Process.Start(@"Service UI.exe");
Task.Run(async () => await IPCC.Connect("Service_Start"));
}
protected override void OnStop()
{
logWriter.LogWrite($"Service stopped running on {DateTime.Now}", MessageLevels.Info);
Task.Run(async () => await IPCC.Connect("Service_Stop"));
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
logWriter.LogWrite($"Service started running on {DateTime.Now}", MessageLevels.Info);
}
}
}
Server.cs
namespace InterProcessCommunication
{
public class Server : IDisposable
{
public LogWriter Logwriter = new LogWriter();
public bool ServerConnectionSuccessfull;
public NamedPipeServerStream ServerPipe;
private bool disposed = false;
public Server() { }
public async Task Connect()
{
try
{
while (true)
{
ServerPipe = new NamedPipeServerStream("Pipe", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Logwriter.LogWrite("Waiting for client connection...", MessageLevels.Info);
await ServerPipe.WaitForConnectionAsync();
ServerConnectionSuccessfull = true;
Logwriter.LogWrite("Client connected.", MessageLevels.Info);
// Read the messages from the client
string message = await ReadMessages();
Logwriter.LogWrite($"Received from client: {message}", MessageLevels.Info);
// Close the connection
ServerPipe.Disconnect();
ServerPipe.Dispose();
}
}
catch (Exception ex)
{
Logwriter.LogWrite($"Exception: {ex.Message}", MessageLevels.Error);
Logwriter.LogWrite($"Stack Trace: {ex.StackTrace}", MessageLevels.Error);
}
}
public void Dispose()
{
if (!disposed)
{
try
{
ServerPipe?.Dispose();
}
catch (Exception ex)
{
Logwriter.LogWrite($"Exception during Dispose: {ex.Message}", MessageLevels.Error);
Logwriter.LogWrite($"Stack Trace: {ex.StackTrace}", MessageLevels.Error);
}
finally
{
disposed = true;
}
}
}
public async Task<string> ReadMessages()
{
try
{
if (ServerPipe == null || !ServerPipe.IsConnected)
{
Logwriter.LogWrite("Pipe server is not initialized.", MessageLevels.Error);
return null;
}
using var sr = new StreamReader(ServerPipe);
string message = await sr.ReadToEndAsync();
return message;
}
catch (IOException ioEx)
{
Logwriter.LogWrite($"IOException: {ioEx.Message}", MessageLevels.Error);
Logwriter.LogWrite($"Stack Trace: {ioEx.StackTrace}", MessageLevels.Error);
return null;
}
catch (Exception ex)
{
Logwriter.LogWrite($"Exception: {ex.Message}", MessageLevels.Error);
Logwriter.LogWrite($"Stack Trace: {ex.StackTrace}", MessageLevels.Error);
return null;
}
}
}
}
Client.cs
namespace InterProcessCommunication
{
public class Client
{
public LogWriter Logwriter = new LogWriter();
public bool ClientConnectionSuccessfull;
public Client() { }
public async Task Connect(string message)
{
while (!ClientConnectionSuccessfull)
{
try
{
using (var client = new NamedPipeClientStream(".", "Pipe", PipeDirection.Out, PipeOptions.None))
{
Logwriter.LogWrite("Connecting to server...", MessageLevels.Info);
await client.ConnectAsync();
Logwriter.LogWrite("Connected to server.", MessageLevels.Info);
ClientConnectionSuccessfull = true;
// write data through the pipe
await SendMessages(client, message);
}
}
catch (Exception ex)
{
Logwriter.LogWrite($"Error Message: {ex}", MessageLevels.Error);
await Task.Delay(5000); // Retry after 5 seconds if the connection fails
ClientConnectionSuccessfull = false;
}
}
}
public async Task SendMessages(NamedPipeClientStream target,string message)
{
try
{
using var sw = new StreamWriter(target);
sw.AutoFlush = true;
//sw.WriteLine(message);
await sw.WriteLineAsync(message);
}
catch (Exception ex)
{
Logwriter.LogWrite($"Exception: {ex.Message}", MessageLevels.Error);
Logwriter.LogWrite($"Stack Trace: {ex.StackTrace}", MessageLevels.Error);
}
}
}
}
For my case, what I am trying to do is to have an open connection where the client sends messages at different times (based on conditional statements) to the server and the server tells the WPF what balloontips to show and what icon to show (like start service display icon with information balloontip; error when service is down and an icon will have cross with error balloontip; etc.).
I can't seem to get it to work. What could be the issue in my code?
Example issues I get:
Thursday, 2024-07-25 13:35:55 LevelI: Service initiated on 25/07/2024 13:35:55
Thursday, 2024-07-25 13:35:55 LevelI: Service will initiate the WPF on 25/07/2024 13:35:55
Thursday, 2024-07-25 13:35:55 LevelI: Connecting to server...
Thursday, 2024-07-25 13:35:55 LevelI: Waiting for client connection...
Thursday, 2024-07-25 13:35:55 LevelI: Connected to server.
Thursday, 2024-07-25 13:35:55 LevelI: Client connected.
Thursday, 2024-07-25 13:35:55 LevelI: Received from client: Service_Start
Thursday, 2024-07-25 13:35:55 LevelI: Waiting for client connection...
Thursday, 2024-07-25 13:36:00 LevelI: Service started running on 25/07/2024 13:36:00
Thursday, 2024-07-25 13:36:05 LevelI: Service started running on 25/07/2024 13:36:05
Thursday, 2024-07-25 13:36:10 LevelI: Service started running on 25/07/2024 13:36:10
Thursday, 2024-07-25 13:36:15 LevelI: Service started running on 25/07/2024 13:36:15
Thursday, 2024-07-25 13:36:20 LevelI: Service started running on 25/07/2024 13:36:20
Thursday, 2024-07-25 13:36:25 LevelI: Service started running on 25/07/2024 13:36:25
Thursday, 2024-07-25 13:36:30 LevelI: Service started running on 25/07/2024 13:36:30
Thursday, 2024-07-25 13:36:35 LevelI: Service started running on 25/07/2024 13:36:35
Thursday, 2024-07-25 13:36:40 LevelI: Service started running on 25/07/2024 13:36:40
Thursday, 2024-07-25 13:36:45 LevelI: Service started running on 25/07/2024 13:36:45
Edit 1:
I have tried to start simple and go from there. However, the approach doesn't seem to work by allowing the connection to be open (I will later include when it should dispose of that connection).
Here are my codes:
Server.cs
LogWriter logWriter = new LogWriter();
private string _pipename;
private NamedPipeServerStream _pipe;
public Server(string pipename)
{
_pipename = pipename;
}
public async Task<string?> ConnectAndReadMessage()
{
using (_pipe = new NamedPipeServerStream(_pipename))
{
//Console.WriteLine("Waiting for connection...");
logWriter.LogWrite("Waiting for connection...", MessageLevels.Info);
_pipe.WaitForConnection();
//await ReadMessages();
while (true)
{
using (var reader = new StreamReader(_pipe))
{
string? line = await reader.ReadLineAsync();
//Console.WriteLine($"Received from client: {line}");
logWriter.LogWrite($"Received from client: {line}", MessageLevels.Info);
return line;
}
}
}
}
Client.cs
public class Client
{
private string _pipename;
private NamedPipeClientStream _pipe;
LogWriter logWriter = new LogWriter();
public Client(string pipename)
{
_pipename = pipename;
}
public async Task ConnectAndSendMessage(string message)
{
using (_pipe = new NamedPipeClientStream(".", _pipename, PipeDirection.Out))
{
_pipe.Connect();
using (var writer = new StreamWriter(_pipe))
{
writer.AutoFlush = true;
writer.WriteLine(message);
//await writer.WriteLineAsync(message);
logWriter.LogWrite($"Sending to the server: {message}", MessageLevels.Info);
}
}
}
}
App.xaml.cs
public partial class App : Application
{
public LogWriter logwriter = new LogWriter();
System.Windows.Forms.NotifyIcon nIcon = new System.Windows.Forms.NotifyIcon();
Server IPCS = new Server("testpipe");
protected override async void OnStartup(StartupEventArgs e)
{
nIcon.Icon = new Icon($@"{AppDomain.CurrentDomain.BaseDirectory}/app.ico");
nIcon.Text = "Service UI";
MainWindow mainWindow = new MainWindow();
mainWindow.Hide(); // Start hidden
// Start listening to the server messages in a background task
await Task.Run(() => ServerMessages());
base.OnStartup(e);
}
private async Task ServerMessages()
{
//await IPCS.ConnectAndReadMessage();
string serverMessage = await IPCS.ConnectAndReadMessage();
logwriter.LogWrite($"Client sent this message: {serverMessage}", MessageLevels.Info);
while (true)
{
//string serverMessage = await IPCS.ReadMessages();
//string serverMessage = await IPCS.ConnectAndReadMessage();
//logwriter.LogWrite($"Client sent this message: {serverMessage}", MessageLevels.Info);
if (serverMessage == "Service_Start")
{
Dispatcher.Invoke(() =>
{
nIcon.Visible = true;
nIcon.ShowBalloonTip(5000, "Service UI", "UI will open up Shortly", System.Windows.Forms.ToolTipIcon.Info);
nIcon.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
nIcon.ContextMenuStrip.Items.Add("Settings", Image.FromFile(@"Resources\cogs_icon.ico"), OnSettingsClicked);
nIcon.ContextMenuStrip.Items.Add("Exit", Image.FromFile(@"Resources\exit_icon.ico"), OnExitClicked);
// This section is about selecting the View MainWindow and running the application:
MainWindow = new MainWindow();
MainWindow.Show();
logwriter.LogWrite("Application is running now", MessageLevels.Info);
//base.OnStartup(e);
});
}
else if (serverMessage == "Service_Stop")
{
Dispatcher.Invoke(() =>
{
nIcon.Dispose();
//base.OnExit(e);
//IPCS.Dispose();
//base.OnExit(e);
MainWindow.Hide();
});
}
}
}
}
Service.cs
public partial class Service : ServiceBase
{
Timer tmr = new Timer();
LogWriter logWriter = new LogWriter();
Client IPCC = new Client("testpipe");
public Service()
{
InitializeComponent();
this.ServiceName = "Service";
}
protected override void OnStart(string[] args)
{
logWriter.LogWrite($"Service initiated on {DateTime.Now}", MessageLevels.Info);
tmr.Elapsed += new ElapsedEventHandler(OnElapsedTime);
tmr.Interval = 5000;
tmr.Enabled = true;
logWriter.LogWrite($"Service will initiate the WPF on {DateTime.Now}", MessageLevels.Info);
Process.Start(@"Service UI.exe");
Thread.Sleep(5000);
Task.Run(async () => await IPCC.ConnectAndSendMessage("Service_Start"));
}
protected override void OnStop()
{
logWriter.LogWrite($"Service stopped running on {DateTime.Now}", MessageLevels.Info);
Task.Run(async () => await IPCC.ConnectAndSendMessage("Service_Stop"));
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
logWriter.LogWrite($"Service started running on {DateTime.Now}", MessageLevels.Info);
}
}
Also from the logs this what I get
Friday, 2024-07-26 15:19:14 LevelI: Service initiated on 26/07/2024 15:19:14
Friday, 2024-07-26 15:19:14 LevelI: Service will initiate the WPF on 26/07/2024 15:19:14
Friday, 2024-07-26 15:19:15 LevelI: Waiting for connection...
Friday, 2024-07-26 15:19:19 LevelI: Service started running on 26/07/2024 15:19:19
Friday, 2024-07-26 15:19:19 LevelI: Sending to the server: Service_Start
Friday, 2024-07-26 15:19:19 LevelI: Received from client: Service_Start
Friday, 2024-07-26 15:19:19 LevelI: Client sent this message: Service_Start
Friday, 2024-07-26 15:19:24 LevelI: Service started running on 26/07/2024 15:19:24
Friday, 2024-07-26 15:19:29 LevelI: Service started running on 26/07/2024 15:19:29
Friday, 2024-07-26 15:19:31 LevelI: Service stopped running on 26/07/2024 15:19:31
I would expect to see Service_Stop but I couldn't find it and also Tray and balloontips do not show up when it get the Service Start.
There are many flaws in your implementation.
See 7) to understand why you are currently receiving only a single message from the client. However, all fixes are important as they fix other relevant bugs. Fixing 7) alone does not make your code work properly. You should be more careful with loops in general. Infinite loops are rarely helpful and rather introduce a bug.
Also consider using the modern WinRT API to send notifications. It allows you to define rich content like buttons and other (interactive) controls: Send a local toast notification from a C# app. The WinForms API and notification service is outdated.
1) Don't use Task.Run
to call an async
method. That's what await
is for:
// False
Task.Run(async () => await IPCC.ConnectAndSendMessage("Service_Start"));
// Correct
await IPCC.ConnectAndSendMessage("Service_Start");
Consider following the common practice to suffix async methods with Async
like ConnectAndSendMessageAsync
for example. It may help you to remember to await
the method, just like you know to await
a .NET method like StreamReader.ReadLineAsync
. Just don't wrap async methods into a Task.Run
.
2) Prefer the asynchronous pipe API. You are not consequently using the asynchronous API.
For example,
use await _pipe.ConnectAsync();
instead of _pipe.Connect();
.
Or use await writer.WriteLineAsync(message);
instead of writer.WriteLine(message);
.
3) Use timeouts to avoid hanging infinitely. Instead, you may want to log the failed connection attempt or throw an exception:
int timeoutInMilliseconds = 5000;
await _pipe.ConnectAsync(timeoutInMilliseconds);
if (!_pipe.IsConnected)
{
// TODO::Log the failed attempt or throw
}
4) Avoid while(true)
(infinite loops) to prevent your application from hanging. Use a reasonable condition to break out. In your case you only want to read from the pipe as long the client is connected:
while (_pipe.IsConnected)
{
// TODO::Read from PipeStream
}
5) Do not create a new PipeStream
for each write and reaoperationon. Those appear to be simple streams. Under the hood they use system expensive resources in order to establish the pipes and to share them between processes. This is not cheap at all.
For this reason, you should instantiate the PipeStream
once and store it in a field. Then let the owner implement IDisposable
and dispose the streams from the Dispose
method.
// False
class Server
{
public async Task<string?> ConnectAndReadMessage()
{
using (_pipe = new NamedPipeServerStream(_pipename))
{
...
}
}
}
// Correct
class Server : IDisposable
{
private readonly NamedPipeServerStream _pipe;
public Server()
{
_pipe = new NamedPipeServerStream(_pipename);
}
public async Task<string?> ConnectAndReadMessageAsync()
{
_pipe.WaitForConnection();
}
// Implement the common Dispose pattern instead of this simple version
public void Dispose()
{
_pipe.Dispose();
}
}
6) Don't start a background tread only to post the complete work back to the UI thread using the Dispatcher
. That's totally pointless and only wastes resources and slows down your application:
App.xaml
// False
// See 1) for this anti-pattern
await Task.Run(() => ServerMessages());
private async Task ServerMessage()
{
string serverMessage = await IPCS.ConnectAndReadMessage();
// See 4) for this anti-pattern.
// Beside that, the infinite loop is totally useless.
// You are only calling IPCS.ConnectAndReadMessage once outside this loop.
// Then you send infinite notifications
// and you show an infinite number of MainWindow instances.
// Be more careful when writing loops.
// Pay extra attention when you believe you need an infinite loop
while (true)
{
// Pointless as basically the complete method executes on the UI thread
Dispatcher.Invoke(() =>
{
// Do something with 'serverMessage' (infinite times)
// Show notifications (infinite times)
// Show MainWindow (infinite times)
});
}
}
// Correct
await ServerMessages();
private async Task ServerMessage()
{
string serverMessage = await IPCS.ConnectAndReadMessage();
// Do something with 'serverMessage' 1x
// Show notifications 1x
// Show MainWindow 1x
}
7) You break the infinite loop in ConnectAndReadMessage
after a single read and immediately return to the caller. That's the reason for your original problem: the missing "Service_Stopped" message. Instead, convert the ConnectAndReadMessage
message into a generator so that you can consume the read lines via a foreach
:
// False
public async Task<string?> ConnectAndReadMessage()
{
// See 5) for this wrong lifetime management of the PipeStream
using (_pipe = new NamedPipeServerStream(_pipename))
{
_pipe.WaitForConnection();
// Infinite loop (see 4) to learn how to fix this particular case)
while (true)
{
// See 5) for this wrong lifetime management of the Stream
using (var reader = new StreamReader(_pipe))
{
string? line = await reader.ReadLineAsync();
// Break out of the loop after a single read.
// As a consequence, you will miss other client messages
return line;
}
}
}
}
// Correct
public async IAsyncEnumerable<string?> ConnectAndReadMessageAsync()
{
while (_pipe.IsConnected)
{
string? line = await _reader.ReadLineAsync();
yield return line;
}
}
// And consume it as follows:
// Wait asynchronously and read each message as it arrives.
await foreach (string readLine in ConnectAndReadMessageAsync)
{
}
8) You must wait for the reader to finish reading from the PipeStream
before sending new data. Call PipeStream.WaitForPipeDrain
before writing to the stream.
9) ServiceBase.OnStart
must be anticipated to be called more than once. A service can be stopped and restarted or paused and resumed (by the user or by the OS) multiple times.
In your case, every call to OnStart
creates and starts a new process of "Service UI.exe". You definitely don't want this. You can start the process from the constructor. Or store the returned Process
instance and kill it from the ServiceBase.OnStop
method override.
10) It's your responsibility to dispose the service e.g. call Dispose
from your derived type. This is where you should also dispose your Client
.
11) Be aware that if your message exchange uses a different word length than a per-line convention you need a protocol to ensure that you always get the complete data. This also includes proper decoding. Just be aware that you have to implement additional message handling if you write byte arrays to the stream or continuous data in general.
There might be more issues with your code, but honestly, I'm getting tired.
The following fixes show a properly working pipe communication and usage of async/await and eliminates all the aforementioned anti-patterns. Pay extra attention to the Server
and Client
classes. Their usage is self explanatory.
App.xaml.cs
public partial class App : Application
{
private const string PipeName = "testpipe";
private readonly LogWriter logwriter = new LogWriter();
System.Windows.Forms.NotifyIcon nIcon = new System.Windows.Forms.NotifyIcon();
private readonly Server pipeServer = new Server(App.PipeName);
protected override async void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
nIcon.Icon = new Icon($@"{AppDomain.CurrentDomain.BaseDirectory}/app.ico");
nIcon.Text = "Service UI";
MainWindow mainWindow = new MainWindow();
mainWindow.Hide(); // Start hidden
// Start listening to the server messages in a background task
await HandleClientMessagesAsync();
this.pipeServer.Dispose();
base.OnStartup(e);
}
private async Task HandleClientMessagesAsync()
{
// Wait non-blocking for new messages to arrive (with the help of IAsyncEnumerable)
await foreach (string clientMessage in this.pipeServer.EnumerateMessagesAsync(isIdleIfDisconnectedEnabled: false))
{
logwriter.LogWrite($"Client sent this message: {clientMessage}", MessageLevels.Info);
if (clientMessage == "Service_Start")
{
nIcon.Visible = true;
nIcon.ShowBalloonTip(5000, "Service UI", "UI will open up Shortly", System.Windows.Forms.ToolTipIcon.Info);
nIcon.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
nIcon.ContextMenuStrip.Items.Add("Settings", Image.FromFile(@"Resources\cogs_icon.ico"), OnSettingsClicked);
nIcon.ContextMenuStrip.Items.Add("Exit", Image.FromFile(@"Resources\exit_icon.ico"), OnExitClicked);
// This section is about selecting the View MainWindow and running the application:
this.MainWindow = new MainWindow();
this.MainWindow.Show();
logwriter.LogWrite("Application is running now", MessageLevels.Info);
}
else if (clientMessage == "Service_Stop")
{
this.nIcon.Dispose();
this.pipeServer.Dispose();
this.MainWindow.Hide();
}
}
}
}
Service.cs
public partial class Service : ServiceBase
{
private const string PipeName = "testpipe";
public bool IsDisposed { get; private set; }
private readonly Timer tmr = new Timer();
private readonly LogWriter logWriter = new LogWriter();
private readonly Client pipeClient = new Client(Service.PipeName);
public Service()
{
InitializeComponent();
this.ServiceName = "Service";
logWriter.LogWrite($"Service will initiate the WPF on {DateTime.Now}", MessageLevels.Info);
_ = Process.Start(@"Service UI.exe");
}
protected override void Dispose(bool isDisposing)
{
if (!this.IsDisposed)
{
if (isDisposing)
{
this.pipeClient.Dispose();
this.tmr.Dispose();
}
this.IsDisposed = true;
}
base.Dispose(isDisposing);
}
protected override void OnShutdown()
{
Dispose(true);
}
protected override async void OnStart(string[] args)
{
logWriter.LogWrite($"Service initiated on {DateTime.Now}", MessageLevels.Info);
tmr.Elapsed += new ElapsedEventHandler(OnElapsedTime);
tmr.Interval = 5000;
tmr.Enabled = true;
// Why this Thread Sleep??
// Prefer Task.Delay - IF THIS IS TRUELY NECESSARY!?
//Thread.Sleep(5000);
await Task.Delay(TimeSpan.FromSeconds(5));
await this.pipeClient.SendMessageAsync("Service_Start");
}
protected override async void OnStop()
{
logWriter.LogWrite($"Service stopped running on {DateTime.Now}", MessageLevels.Info);
await pipeClient.SendMessageAsync("Service_Stop");
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
logWriter.LogWrite($"Service started running on {DateTime.Now}", MessageLevels.Info);
}
}
Server.cs
public class Server : IDisposable
{
public bool IsDisposed { get; private set; }
private readonly string _pipename;
private readonly NamedPipeServerStream _pipe;
private readonly StreamReader _pipeReader;
private readonly LogWriter logWriter = new LogWriter();
public Server(string pipename)
{
_pipename = pipename;
_pipe = new NamedPipeServerStream(_pipename);
_pipeReader = new StreamReader(_pipe);
}
public async IAsyncEnumerable<string> EnumerateMessagesAsync(bool isIdleIfDisconnectedEnabled)
{
do
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
logWriter.LogWrite("Waiting for connection...", MessageLevels.Info);
_pipe.WaitForConnection();
// Stop when the client has been disconnected
while (_pipe.IsConnected)
{
string? receivedLine = await _pipeReader.ReadLineAsync();
logWriter.LogWrite($"Received from client: {receivedLine}", MessageLevels.Info);
yield return receivedLine;
}
yield return "Connection closed";
} while (isIdleIfDisconnectedEnabled); // Optionally wait for another connection or return if isIdleIfDisconnectedEnabled is FALSE
}
public void Disconnect()
=> _pipe.Dispose();
protected virtual void Dispose(bool disposing)
{
if (!this.IsDisposed)
{
if (disposing)
{
_pipe.Disconnect();
_pipe.Dispose();
_pipeReader.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
this.IsDisposed = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~Server()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
Client.cs
public class Client : IDisposable
{
private static readonly TimeSpan ConnectionTimeout = TimeSpan.FromSeconds(5);
public bool IsDisposed { get; private set; }
private readonly string _pipename;
private readonly NamedPipeClientStream _pipe;
private StreamWriter _pipeWriter;
public Client(string pipename)
{
_pipename = pipename;
_pipe = new NamedPipeClientStream(".", _pipename, PipeDirection.Out);
}
public async Task SendMessageAsync(string message)
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
// Server could have disconnected the pipe since the last send.
// Try to reconnect.
if (!_pipe.IsConnected)
{
// Wait for the server to accept the connection.
// Use a timeout (or CancellationToken) to prevent hanging
await _pipe.ConnectAsync((int)ConnectionTimeout.TotalMilliseconds);
if (!_pipe.IsConnected)
{
logWriter.LogWrite("Failed to connect to pipe server", MessageLevels.Info);
}
if (_pipeWriter is null)
{
_pipeWriter = new StreamWriter(_pipe) { AutoFlush = true };
}
}
// Important: wait until the receiver has read all the content from the stream
_pipe.WaitForPipeDrain();
await _pipeWriter.WriteLineAsync(message);
logWriter.LogWrite($"Sending to the server: {message}", MessageLevels.Info);
}
public void Disconnect()
=> _pipe.Dispose();
protected virtual void Dispose(bool disposing)
{
if (!this.IsDisposed)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
_pipe.Dispose();
_pipeWriter.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
this.IsDisposed = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~Client()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}