问题
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.
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;
}
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);
}
Please feel free to discuss over this topic which I think is a very common use case among Windows desktop developers. Thank you.
回答1:
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();
}
}
}
Interace
namespace example
{
public interface IMyService
{
void MyMethod();
}
}
Implementation of the service
namespace example
{
public class MyService : IMyService
{
public MyService()
{
// on signleton service this only gets invoked once
}
public void MyMethod()
{
// your imolmentation here
}
}
}
Some online reference: https://dzone.com/articles/dependency-injection-in-net-core-console-applicati
https://code-maze.com/dependency-inversion-principle/
来源:https://stackoverflow.com/questions/59706160/c-sharp-dot-net-core-single-instance-app-passing-parameters-to-first-instance