C# dot net core 单实例应用程序将参数传递给第一个实例 [英] C# dot net core single instance app passing parameters to first instance

查看:18
本文介绍了C# dot net core 单实例应用程序将参数传递给第一个实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Recently I decided to migrate one of my WPF Windows desktop apps written in C# and targeting .NET Framework 4.5 to the newest .NET Core 3.1. All was good until I had to add support for single instance application while being able to pass any parameters from the second instance to the first running instance. My previous WPF implementation for single instance application was using System.Runtime.Remoting which is not available in .NET Core. Therefore I had to do something new. Below is the implementation that I came up with. It works great but I feel it can be improved. Please, feel free to discuss and improve the proposed solution.

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

I created a SingleInstanceService which is using a semaphore to signal if it is the first instance or not. If it is the first instance, I create a TcpListener and I wait indefinitely for any arguments passed from a second instance. If a second instance is started, then I send the parameters of the second instance to the first listening instance and then I exit the second instance.

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 中,我有以下代码:

Then in App.xaml.cs, I have the following code:

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 桌面开发人员中非常常见的用例.谢谢.

Please feel free to discuss over this topic which I think is a very common use case among Windows desktop developers. Thank you.

推荐答案

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

Use dependency injection. You will need to define a interface then implement the interface. In startup code, you will need to add the interface and it's implementation as singleton service.

在您的实现构造函数中,您可以放置​​您只想运行一次的代码.这将持续对象的生命周期.

In your implementation constructor can you put the code that you want to run only once. This will last lifetime of the object.

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

There are other type of injections, transient and scoped but for your use case you may only need singleton.

Program.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>();
        }
    }
}

Startup.cs

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

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

    }
}

互动

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/

这篇关于C# dot net core 单实例应用程序将参数传递给第一个实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆