C#: 显示来自Windows服务的实时消息的图形用户界面

6
我已经编写了一个C# Windows服务,可以将消息写入自定义的EventLog或任意数量的文件中。这些消息都带有某些优先级标记(例如,只有错误和警告会存储在EventLog中,但如果需要,可以将更多消息存储到文件中)。
现在,我想创建一个GUI,可以实时监听这些消息并显示它们。允许用户观看当前消息(以他们所需的优先级水平),而无需将所有内容存储到文件中。我假设这是一个独立的程序,并具有一些钩子连接到服务,但我不确定从何处开始。
这是我第一个真正的Windows服务,所以我好像缺少一些关键字来找出如何做到这一点...是否有任何代码示例、教程、参考资料等来完成这样的事情?

更新
有很多有用的答案,我喜欢有多种解决问题的方式!我想实现一个自主托管的基于WCF的解决方案。目前我对细节还不是很了解,因为我正在学习WCF(我相信它在其他项目中将非常有用)......但到目前为止,我发现这里的视频here作为入门教程最有帮助。

8个回答

8
您可以使用Windows通讯基础设施(Windows Communication Foundation)为Windows服务注册事件的方式来处理。当出现错误时,它会触发该事件并向您的WinForms应用程序发送通知,这被称为双工合同。以下是相关链接: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/0eb69998-0388-4731-913e-fb205528d374/ http://msdn.microsoft.com/en-us/library/ms731184.aspx 实际上,您还可以通过此方式让多个应用程序同时监听该事件,以便在屏幕上显示或记录到其他应用程序中,而这两个外部应用程序互相无需知晓。

好的答案,因为使用了平台标准机制并提供了全面的文档参考。 - Tom W

3
我知道这个已经被提到过了,但要使用Windows Communication Foundation(WCF)。具体来说,使用发布-订阅框架,该框架由Juval Lowy开发,他是Programming WCF Services一书的作者。详细信息在这篇出色的MSDN文章中描述,源代码可在Lowy的网站上免费获取。
这个框架的好处在于它将发布者,例如您的Windows服务,与任何订阅者(例如您的GUI)分离。发布者“发布”感兴趣的事件,这些事件始终可用于Pub/Sub服务。从发布者的角度来看,是否有任何订阅者并不重要。Pub/Sub服务负责将事件路由到所有已注册的订阅者。通过这种方式,您的Windows服务会在事件发生时发布事件,您的GUI将在加载/退出时订阅/取消订阅Pub/Sub服务,并且Pub/Sub服务将通知您的GUI随着事件的发生而发生的情况。 我在我的项目中使用了这个设置,它运行得非常好。

2

alt text

我之前使用过BitFactory Logger,它有一个套接字日志记录器可以用于此目的。


2
您所描述的是进程间通信,这可能会变得混乱。
最简单、最优雅但可能反应最慢的方法是让服务将条目写成小型文本文件(或附加到日志),并让您的GUI使用FileSystemWatcher检测新文件或更新日志文件,并读取该文件。您必须确保服务以“共享”方式打开文件进行附加,允许只读访问而不影响写入。否则,您将阻止一个进程或另一个进程,可能导致丢失消息。
进程可以通过一些内置管道进行通信。如果您的服务将消息写入其StandardOutput管道,则GUI可以远程附加侦听器并在写入消息时接收事件。这可能是实现您想要的非文件方式中最优雅的方法。研究Process类,特别是OutputDataReceived事件。您将需要通过某些唯一标识信息从GUI中查找进程,使用GetProcess()方法。

个人而言,我喜欢使用管道,但这可能是因为我最熟悉它们了。你也可以使用命名管道而不是标准管道。 - Tergiver
文件方法实际上是我想到的一个想法,但找不到一个干净的方法来实现它。FileSystemWatcher看起来是一个非常好的解决方案!我认为管道不能为我提供一个干净的解决方案...如果我理解正确,服务将需要在启动时了解GUI(以重定向其标准输出),这一点我不确定...这是正确的吗,还是我漏掉了什么? - chezy525
如果您使用命名管道,则不需要。您可以使用全局唯一名称(通常类似于“\.\pipe\Company Name\Application Name\Pipe Name”)创建管道。服务在启动时创建管道,GUI应用程序可以通过名称连接到该管道。 - Tergiver

0
你需要寻找“同步”和“进程间通信”。在你的情况下,服务将使用全局事件或信号量来发出数据存在的信号,GUI进程将检查事件/信号量状态并从事件日志或文件中读取更新。
存在更复杂的场景,但以上是一个很好的起点。

0

观察者模式!

也许可以为所有可观察的模型设置一个委托,您可以将其与您的服务连接起来?


0

使用IPC通道的.NET远程调用。


0
我发现使用命名管道与系统托盘应用程序进行通信是从Windows服务显示通知的最简单方法。这是因为在Windows 10中,服务以不同于登录用户的权限运行,因此通知应用程序需要通过IPC与服务进行通信。
您可以将此放入服务器中:
using System;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleServerApp
{
    class Program
    {
        static void Main(string[] args)
        {
            StartServer();
            Task.Delay(1000).Wait();
        }

        static void StartServer()
        {
            Task.Factory.StartNew(() =>
            {
                var server = new NamedPipeServerStream("PipesOfPiece");
                server.WaitForConnection();
                StreamReader reader = new StreamReader(server);
                StreamWriter writer = new StreamWriter(server);
                while (true)
                {
                    var line = reader.ReadLine();
                    writer.WriteLine(String.Join("", line.Reverse()));
                    writer.Flush();
                }
            });
        }
    }
}

然后将此放入您的客户端:

using System;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //Client
            var client = new NamedPipeClientStream("PipesOfPiece");
            client.Connect();
            StreamReader reader = new StreamReader(client);
            StreamWriter writer = new StreamWriter(client);

            while (true)
            {
                string input = Console.ReadLine();
                if (String.IsNullOrEmpty(input)) break;
                writer.WriteLine(input);
                writer.Flush();
                Console.WriteLine(reader.ReadLine());
            }
        }
    }
}

然后将你的 ConsoleServerApp 更改为 Winforms 应用程序,这样它就可以在 Windows 服务发送消息时显示通知:
    public Form1()
    {
        InitializeComponent();

        StartServer();
        Task.Delay(_threadJoinTimeout).Wait();

    }

    public void DisplayMessage()
    {
        this.notifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
        this.notifyIcon1.BalloonTipText = "Welcomd!";
        this.notifyIcon1.BalloonTipTitle = "Title";
        this.notifyIcon1.ShowBalloonTip(2000);
    }

    void StartServer()
    {
        Task.Factory.StartNew(() =>
        {
            var server = new NamedPipeServerStream("PipesOfPiece");
            server.WaitForConnection();
            StreamReader reader = new StreamReader(server);

            while (true)
            {
                var line = reader.ReadLine();
                DisplayMessage();
            }
        });
    }

然后将ConsoleClientApp放入您的Windows服务中。

有关管道的详细信息,请参见命名管道示例 有关系统托盘应用程序,请参见http://www.tutorialspanel.com/create-system-tray-icon-windows-forms-application-using-c-vb-net/#:~:text=Below%20is%20an%20example%20of%20how%20to%20create,Step%203.%20Add%20an%20icon%20to%20the%20NotifyIcon 以下是使用TopShelf NuGet包的提示,该包允许您将Windows服务调试为控制台应用程序:https://www.codeproject.com/Articles/881511/SignalR-with-Self-hosted-Windows-Service


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接