Я пытаюсь создать службу с WPF, где служба будет взаимодействовать с WPF, сообщая WPF отображать панель значков и подсказку.
Я разработал следующие коды:
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);
}
}
}
Сервисный файл
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);
}
}
}
Сервер.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;
}
}
}
}
Клиент.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);
}
}
}
}
В моем случае я пытаюсь создать открытое соединение, в котором клиент отправляет сообщения в разное время (на основе условных операторов) на сервер, а сервер сообщает WPF, какие всплывающие подсказки показывать и какой значок показывать (например, значок запуска службы с всплывающей подсказкой; ошибка, когда служба не работает, и на значке отображается крестик с всплывающей подсказкой об ошибке и т. д.);
Кажется, я не могу заставить его работать. В чем может быть проблема в моем коде?
Примеры проблем, которые я получаю:
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
Редактировать 1:
Я попытался начать с простого и двигаться дальше. Однако этот подход, похоже, не работает, позволяя открыть соединение (позже я укажу, когда ему следует избавиться от этого соединения).
Вот мои коды:
Сервер.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;
}
}
}
}
Клиент.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();
});
}
}
}
}
Сервис.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);
}
}
Также из журналов это то, что я получаю
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
Я ожидал увидеть Service_Stop, но не смог его найти, а также лоток и всплывающие подсказки не отображаются при запуске службы.
То же самое относится ко всем вашим вызовам Task.Run. Почему вы помещаете вызов ожидаемого метода в Task.Run? Тебе не следует этого делать. Просто дождитесь выполнения задачи. Вам также не следует использовать старый API WinForms (Windows XP) для отправки уведомлений. Если вы используете современный WinRT API, вы получаете «оригинальные» уведомления WIndows, и теперь это уведомление может иметь богатое содержимое. Например, вы можете добавить к ним кнопки или другие элементы управления. Отправьте локальное всплывающее уведомление из приложения C#
Конечно, да. Вот почему я упомянул или рекомендовал это. По ссылке вы найдете полный пример, которому вы можете следовать, а затем адаптировать его к своим потребностям.
Первая проблема заключается в том, что в вашем методе подключения вы создаете серверный канал, читаете одно сообщение, а затем уничтожаете канал. Вероятно, вы хотите, чтобы серверный канал прослужил долго. Я бы, вероятно, использовал выделенный поток для сервера и событие для информирования пользовательского интерфейса о сообщениях.
еще одна потенциальная проблема, по-видимому, заключается в отсутствии структуры сообщения. Без формирования сообщения возможно, что автор написал более одного сообщения или только половину сообщения.
Конечно, это можно сделать самостоятельно, но я предпочитаю использовать библиотеку для всех сложных вещей. Часто в сочетании с библиотекой сериализации, такой как Json или Protobuf, для отправки произвольных объектов, а не только строк. Библиотеки можно в основном классифицировать как библиотеки типа «запрос-ответ» или «публикация-подписка». http и gRPC — примеры первого, MQTT — пример второго, но их гораздо больше. Кажется, в этом посте есть еще несколько предложений.
В вашей реализации много недостатков.
См. 7), чтобы понять, почему вы в настоящее время получаете только одно сообщение от клиента. Однако все исправления важны, поскольку они исправляют другие важные ошибки. Исправление 7) само по себе не обеспечит правильную работу вашего кода. В целом с циклами следует быть осторожнее. Бесконечные циклы редко бывают полезными и скорее приводят к ошибке.
Также рассмотрите возможность использования современного API WinRT для отправки уведомлений. Он позволяет вам определять богатый контент, такой как кнопки и другие (интерактивные) элементы управления: Отправьте локальное всплывающее уведомление из приложения C#. API WinForms и служба уведомлений устарели.
1) Не используйте Task.Run
для вызова метода async
. Вот для чего нужен await
:
// False
Task.Run(async () => await IPCC.ConnectAndSendMessage("Service_Start"));
// Correct
await IPCC.ConnectAndSendMessage("Service_Start");
Подумайте о том, чтобы следовать общепринятой практике и добавлять к асинхронным методам суффикс Async
, например ConnectAndSendMessageAsync
. Это может помочь вам запомнить await
метод точно так же, как вы знаете await
метод .NET, такой как StreamReader.ReadLineAsync
. Только не заключайте асинхронные методы в Task.Run
.
2) Предпочитайте API асинхронного канала. Следовательно, вы не используете асинхронный API.
Например,
используйте await _pipe.ConnectAsync();
вместо _pipe.Connect();
.
Или используйте await writer.WriteLineAsync(message);
вместо writer.WriteLine(message);
.
3) Используйте таймауты, чтобы избежать бесконечного зависания. Вместо этого вы можете зарегистрировать неудачную попытку подключения или создать исключение:
int timeoutInMilliseconds = 5000;
await _pipe.ConnectAsync(timeoutInMilliseconds);
if (!_pipe.IsConnected)
{
// TODO::Log the failed attempt or throw
}
4) Избегайте while(true)
(бесконечных циклов), чтобы приложение не зависало. Используйте разумные условия, чтобы вырваться. В вашем случае вы хотите читать из канала только до тех пор, пока клиент подключен:
while (_pipe.IsConnected)
{
// TODO::Read from PipeStream
}
5) Не создавайте новый PipeStream
для каждой записи и повторной операции. Кажется, это простые потоки. Под капотом они используют дорогостоящие системные ресурсы для организации каналов и распределения их между процессами. Это совсем не дешево.
По этой причине вам следует создать экземпляр PipeStream
один раз и сохранить его в поле. Затем позвольте владельцу реализовать IDisposable
и удалить потоки из метода Dispose
.
// 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) Не начинайте фоновый шаг только для того, чтобы опубликовать всю работу обратно в ветку пользовательского интерфейса, используя Dispatcher
. Это совершенно бессмысленно, только тратит ресурсы и замедляет работу вашего приложения:
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) Вы прерываете бесконечный цикл в ConnectAndReadMessage
после одного чтения и немедленно возвращаетесь к вызывающей стороне. Это причина вашей первоначальной проблемы: отсутствие сообщения «Service_Stopped». Вместо этого преобразуйте сообщение ConnectAndReadMessage
в генератор, чтобы вы могли использовать прочитанные строки через 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) Прежде чем отправлять новые данные, необходимо дождаться, пока считыватель закончит чтение с PipeStream
. Прежде чем писать в поток, позвоните PipeStream.WaitForPipeDrain
.
9) Ожидается, что ServiceBase.OnStart
будет вызван более одного раза. Служба может быть остановлена и перезапущена или приостановлена и возобновлена (пользователем или ОС) несколько раз.
В вашем случае каждый вызов OnStart
создает и запускает новый процесс «Service UI.exe». Ты определенно этого не хочешь. Запустить процесс можно из конструктора. Или сохраните возвращенный экземпляр Process
и завершите его переопределением метода ServiceBase.OnStop
.
10) Вы несете ответственность за удаление услуги, например. вызовите Dispose
из производного типа. Сюда же следует распорядиться и своим Client
.
11) Имейте в виду, что если в вашем обмене сообщениями используется длина слова, отличная от построчного соглашения, вам нужен протокол, гарантирующий, что вы всегда получаете полные данные. Это также включает в себя правильное декодирование. Просто имейте в виду, что вам придется реализовать дополнительную обработку сообщений, если вы записываете в поток массивы байтов или непрерывные данные в целом.
Возможно, с вашим кодом возникнут еще проблемы, но, честно говоря, я уже устал.
Следующие исправления показывают правильно работающую канальную связь и использование async/await и устраняют все вышеупомянутые анти-шаблоны. Обратите особое внимание на классы Server
и Client
. Их использование говорит само за себя.
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();
}
}
}
}
Сервис.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);
}
}
Сервер.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);
}
}
Клиент.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);
}
}
В OnStop вы не ждете возврата IPCC.ConnectAndSendMessage. Task.Run является избыточным. Просто дождитесь звонка
IPCC.ConnectAndSendMessage
, и сообщение должно появиться в ваших журналах.