C# dot net core 单实例应用将参数传递到第一个实例

C# dot net core single instance app passing parameters to first instance

提问人:Alexandru Dicu 提问时间:1/13/2020 最后编辑:Mark A. DonohoeAlexandru Dicu 更新时间:5/3/2021 访问量:2033

问:

最近,我决定将用 C# 编写并面向 .NET Framework 4.5 的 WPF Windows 桌面应用之一迁移到最新的 .NET Core 3.1。一切都很好,直到我不得不添加对单实例应用程序的支持,同时能够将任何参数从第二个实例传递到第一个正在运行的实例。我之前对单实例应用程序的 WPF 实现使用的是 System.Runtime.Remoting,这在 .NET Core 中不可用。因此,我必须做一些新的事情。以下是我想出的实现。它效果很好,但我觉得它可以改进。请随时讨论和改进建议的解决方案。

我创建了一个 SingleInstanceService,它使用信号量来指示它是否是第一个实例。如果是第一个实例,我将创建一个 TcpListener,并无限期地等待从第二个实例传递的任何参数。如果启动了第二个实例,则将第二个实例的参数发送到第一个侦听实例,然后退出第二个实例。

internal class SingleInstanceService
{
    internal Action<string[]> OnArgumentsReceived;

    internal bool IsFirstInstance()
    {
        if (Semaphore.TryOpenExisting(semaphoreName, out semaphore))
        {
            Task.Run(() => { SendArguments(); Environment.Exit(0); });
            return false;
        }
        else
        {
            semaphore = new Semaphore(0, 1, semaphoreName);
            Task.Run(() => ListenForArguments());
            return true;
        }
    }

    private void ListenForArguments()
    {
        TcpListener tcpListener = new TcpListener(IPAddress.Parse(localHost), localPort);
        try
        {
            tcpListener.Start();
            while (true)
            {
                TcpClient client = tcpListener.AcceptTcpClient();
                Task.Run(() => ReceivedMessage(client));
            }
        }
        catch (SocketException ex)
        {
            Log.Error(ex);
            tcpListener.Stop();
        }
    }

    private void ReceivedMessage(TcpClient tcpClient)
    {
        try
        {
            using (NetworkStream networkStream = tcpClient?.GetStream())
            {
                string data = null;
                byte[] bytes = new byte[256];
                int bytesCount;
                while ((bytesCount = networkStream.Read(bytes, 0, bytes.Length)) != 0)
                {
                    data += Encoding.UTF8.GetString(bytes, 0, bytesCount);
                }
                OnArgumentsReceived(data.Split(' '));
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex);
        }
    }

    private void SendArguments()
    {
       try
       {
            using (TcpClient tcpClient = new TcpClient(localHost, localPort))
            {
                using (NetworkStream networkStream = tcpClient.GetStream())
                {
                    byte[] data = Encoding.UTF8.GetBytes(string.Join(" ", Environment.GetCommandLineArgs()));
                    networkStream.Write(data, 0, data.Length);                       
                }
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex);
        }
    }

    private Semaphore semaphore;
    private string semaphoreName = $"Global\\{Environment.MachineName}-myAppName{Assembly.GetExecutingAssembly().GetName().Version}-sid{Process.GetCurrentProcess().SessionId}";
    private string localHost = "127.0.0.1";
    private int localPort = 19191;
}

然后在 App.xaml.cs 中,我有以下代码:

protected override void OnStartup(StartupEventArgs e)
{            
   SingleInstanceService singleInstanceService = new SingleInstanceService();

   if (singleInstanceService.IsFirstInstance())
   {
       singleInstanceService.OnArgumentsReceived += OnArgumentsReceived;      
       // Some other calls
   }
   base.OnStartup(e);
}

private void OnArgumentsReceived(string[] args)
{
    // ProcessCommandLineArgs(args);           
}

请随时讨论这个话题,我认为这是 Windows 桌面开发人员中非常常见的用例。谢谢。

C# .net-core 参数传递 单实例

评论

0赞 Pavel Anikhouski 1/13/2020
可以尝试使用它,它在 .NET Core 中可用。您想将哪些参数传递给二审?Mutex
0赞 Alexandru Dicu 1/13/2020
我选择了信号量,因为它是一种信号机制,而不是互斥锁,互斥锁是一种锁定机制。它可以以两种方式完成,但我更喜欢信号量,我发现它更容易用于当前目的。要求是将命令行参数从第二个实例发送到第一个实例。
0赞 Pavel Anikhouski 1/13/2020
另一种选择是使用 p/invoke 函数,就像在这个线程中一样
0赞 Alexandru Dicu 1/13/2020
通过迁移到 .NET Core,我建议不要那么容易地使用 p/invoke。
3赞 Ian Kemp 1/14/2020
我投票决定将这个问题作为题外话来结束,因为它属于 Code Review Stack Exchange。

答:

-5赞 shobhonk 1/22/2020 #1

使用依赖注入。您需要定义一个接口,然后实现该接口。在启动代码中,您需要添加接口并将其实现为单例服务。

在实现构造函数中,是否可以放置只想运行一次的代码。这将持续对象的生存期。

还有其他类型的注入,瞬态和作用域,但对于您的用例,您可能只需要单例。

程序.cs

using System;
using Microsoft.Extensions.DependencyInjection;

namespace example
{
    class Program
    {
        static void Main(string[] args)
        {
            // create service collection
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);

            var serviceProvider = serviceCollection.BuildServiceProvider();

            serviceProvider.GetService<Startup>().Run();
        }

        private static void ConfigureServices(IServiceCollection serviceCollection)
        {
            // add services
            serviceCollection.AddTransient<IMyService, MyService>();

            serviceCollection.AddTransient<Startup>();
        }
    }
}

启动 .cs

namespace example
{
    public class Startup
    {
        private readonly IMyService _myService;
        public Startup(IMyService myService)
        {
            _myService = myService;
        }

        public void Run()
        {
            _myService.MyMethod();
        }

    }
}

Interace(英特莱斯酒店)

namespace example
{
    public interface IMyService
    {
        void MyMethod();
    }
}

服务的实施

 namespace example
    {
        public class MyService : IMyService
        {
            public MyService()
            {
                // on signleton service this only gets invoked once
            }

            public void MyMethod()
            {
                // your imolmentation here 
            }
        }
    }

一些在线参考:https://dzone.com/articles/dependency-injection-in-net-core-console-applicati

https://code-maze.com/dependency-inversion-principle/

评论

1赞 Alexandru Dicu 1/22/2020
对不起,我不明白这与所讨论的主题有什么关系:单实例应用程序并将参数传递给第一个正在运行的实例。