С# dot net core одноэкземплярное приложение, передающее параметры в первый экземпляр

Недавно я решил перенести одно из своих настольных приложений WPF для Windows, написанное на C# и ориентированное на .NET Framework 4.5, на новейшую версию .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. Спасибо.


person Alexandru Dicu    schedule 12.01.2020    source источник
comment
Вы можете попробовать использовать для этого Mutex, он доступен в ядре .NET. Какие аргументы вы хотите передать второму экземпляру?   -  person Pavel Anikhouski    schedule 12.01.2020
comment
Я выбрал семафор, так как это сигнальный механизм, а не мьютекс, который является механизмом блокировки. Это можно сделать обоими способами, но я предпочел семафор, который мне проще использовать для текущей цели. Требование состоит в том, чтобы отправить аргументы командной строки из второго экземпляра в первый экземпляр.   -  person Alexandru Dicu    schedule 12.01.2020
comment
Другой вариант — использовать для этого функции p/invoke, как в этом тема   -  person Pavel Anikhouski    schedule 12.01.2020
comment
При переходе на .NET Core я бы не советовал так просто использовать p/invoke.   -  person Alexandru Dicu    schedule 12.01.2020
comment
Я голосую за то, чтобы закрыть этот вопрос как не относящийся к теме, потому что он относится к Code Review Stack Exchange.   -  person Ian Kemp    schedule 14.01.2020
comment
Используйте внедрение зависимостей.   -  person shobhonk    schedule 22.01.2020
comment
Что и куда вводить? Можете ли вы уточнить, пожалуйста.   -  person Alexandru Dicu    schedule 22.01.2020


Ответы (1)


Используйте внедрение зависимостей. Вам нужно будет определить интерфейс, а затем реализовать интерфейс. В коде запуска вам нужно будет добавить интерфейс и его реализацию как одноэлементную службу.

В конструкторе реализации вы можете поместить код, который хотите запустить только один раз. Это будет длиться всю жизнь объекта.

Существуют и другие типы инъекций, переходные и ограниченные, но для вашего варианта использования вам может понадобиться только синглтон.

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/

person shobhonk    schedule 22.01.2020
comment
Извините, но я не понимаю, как это связано с рассматриваемой темой: приложение с одним экземпляром и передача аргументов первому запущенному экземпляру. - person Alexandru Dicu; 22.01.2020