mirror of
https://github.com/ShareX/ShareX.git
synced 2024-09-30 01:07:21 +13:00
Proper Single Instance Implementation
Reworked single instance code to better handle being launched with context menu. Previously: - On launch, process would try to open an `EventWaitHandle`. If it didn't exist, then process assumed it was first and created it - Multiple processes launching at same time would all see the `EventWaitHandle` did not exist before any process could create it - This led to multiple instances of program running Now a mutex is used: - Processes will all try to open the mutex. Only first one will succeed - Process that opens the mutex will setup the `EventWaitHandle` - Other processes won't get the mutex and will be directed to use the `EventWaitHandle` to pass their command line args to the running instance
This commit is contained in:
parent
538acf30e8
commit
29e8e739e3
3 changed files with 58 additions and 52 deletions
|
@ -35,56 +35,45 @@ namespace SingleInstanceApplication
|
|||
public static class ApplicationInstanceManager
|
||||
{
|
||||
private static Semaphore semaphore;
|
||||
private static string appName = "ShareX";
|
||||
private static string eventName = string.Format("{0}-{1}", Environment.MachineName, appName);
|
||||
private static string semaphoreName = string.Format("{0}{1}", eventName, "Semaphore");
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public static bool CreateSingleInstance(string name, EventHandler<InstanceCallbackEventArgs> callback, string[] args)
|
||||
public static void CreateFirstInstance(EventHandler<InstanceCallbackEventArgs> callback)
|
||||
{
|
||||
string eventName = string.Format("{0}-{1}", Environment.MachineName, name);
|
||||
string semaphoreName = string.Format("{0}{1}", eventName, "Semaphore");
|
||||
bool createdNew;
|
||||
|
||||
InstanceProxy.IsFirstInstance = false;
|
||||
InstanceProxy.CommandLineArgs = args;
|
||||
|
||||
try
|
||||
using (EventWaitHandle eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName, out createdNew))
|
||||
{
|
||||
using (EventWaitHandle eventWaitHandle = EventWaitHandle.OpenExisting(eventName))
|
||||
// Mixing single instance and multi instance (via command line parameter) copies of the program can
|
||||
// result in CreateFirstInstance being called if it isn't really the first one. Make sure this is
|
||||
// really first instance by detecting if EventWaitHandle was created
|
||||
if (createdNew != true)
|
||||
{
|
||||
semaphore = Semaphore.OpenExisting(semaphoreName);
|
||||
semaphore.WaitOne();
|
||||
UpdateRemoteObject(name);
|
||||
|
||||
if (eventWaitHandle != null) eventWaitHandle.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
Environment.Exit(0);
|
||||
semaphore = new Semaphore(1, 1, semaphoreName);
|
||||
ThreadPool.RegisterWaitForSingleObject(eventWaitHandle, WaitOrTimerCallback, callback, Timeout.Infinite, false);
|
||||
|
||||
RegisterRemoteType(appName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
InstanceProxy.IsFirstInstance = true;
|
||||
|
||||
using (EventWaitHandle eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
|
||||
{
|
||||
semaphore = new Semaphore(1, 1, semaphoreName);
|
||||
ThreadPool.RegisterWaitForSingleObject(eventWaitHandle, WaitOrTimerCallback, callback, Timeout.Infinite, false);
|
||||
}
|
||||
|
||||
RegisterRemoteType(name);
|
||||
}
|
||||
|
||||
return InstanceProxy.IsFirstInstance;
|
||||
}
|
||||
|
||||
public static bool CreateSingleInstance(EventHandler<InstanceCallbackEventArgs> callback, string[] args)
|
||||
public static void CreateMultipleInstance(EventHandler<InstanceCallbackEventArgs> callback, string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
return CreateSingleInstance("ShareX", callback, args);
|
||||
}
|
||||
catch
|
||||
InstanceProxy.CommandLineArgs = args;
|
||||
|
||||
using (EventWaitHandle eventWaitHandle = EventWaitHandle.OpenExisting(eventName))
|
||||
{
|
||||
semaphore = Semaphore.OpenExisting(semaphoreName);
|
||||
semaphore.WaitOne();
|
||||
UpdateRemoteObject(appName);
|
||||
|
||||
if (eventWaitHandle != null) eventWaitHandle.Set();
|
||||
}
|
||||
|
||||
return true;
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private static void UpdateRemoteObject(string uri)
|
||||
|
@ -96,7 +85,7 @@ private static void UpdateRemoteObject(string uri)
|
|||
|
||||
if (proxy != null)
|
||||
{
|
||||
proxy.SetCommandLineArgs(InstanceProxy.IsFirstInstance, InstanceProxy.CommandLineArgs);
|
||||
proxy.SetCommandLineArgs(InstanceProxy.CommandLineArgs);
|
||||
}
|
||||
|
||||
ChannelServices.UnregisterChannel(clientChannel);
|
||||
|
@ -122,7 +111,7 @@ private static void WaitOrTimerCallback(object state, bool timedOut)
|
|||
EventHandler<InstanceCallbackEventArgs> callback = state as EventHandler<InstanceCallbackEventArgs>;
|
||||
if (callback == null) return;
|
||||
|
||||
callback(state, new InstanceCallbackEventArgs(InstanceProxy.IsFirstInstance, InstanceProxy.CommandLineArgs));
|
||||
callback(state, new InstanceCallbackEventArgs(InstanceProxy.CommandLineArgs));
|
||||
|
||||
semaphore.Release();
|
||||
}
|
||||
|
|
|
@ -32,27 +32,21 @@ namespace SingleInstanceApplication
|
|||
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
|
||||
internal class InstanceProxy : MarshalByRefObject
|
||||
{
|
||||
public static bool IsFirstInstance { get; internal set; }
|
||||
|
||||
public static string[] CommandLineArgs { get; internal set; }
|
||||
|
||||
public void SetCommandLineArgs(bool isFirstInstance, string[] commandLineArgs)
|
||||
public void SetCommandLineArgs(string[] commandLineArgs)
|
||||
{
|
||||
IsFirstInstance = isFirstInstance;
|
||||
CommandLineArgs = commandLineArgs;
|
||||
}
|
||||
}
|
||||
|
||||
public class InstanceCallbackEventArgs : EventArgs
|
||||
{
|
||||
internal InstanceCallbackEventArgs(bool isFirstInstance, string[] commandLineArgs)
|
||||
internal InstanceCallbackEventArgs(string[] commandLineArgs)
|
||||
{
|
||||
IsFirstInstance = isFirstInstance;
|
||||
CommandLineArgs = commandLineArgs;
|
||||
}
|
||||
|
||||
public bool IsFirstInstance { get; private set; }
|
||||
|
||||
public string[] CommandLineArgs { get; private set; }
|
||||
}
|
||||
}
|
|
@ -268,16 +268,39 @@ private static void Main(string[] args)
|
|||
|
||||
IsMultiInstance = CLI.IsCommandExist("multi", "m");
|
||||
|
||||
if (IsMultiInstance || ApplicationInstanceManager.CreateSingleInstance(SingleInstanceCallback, args))
|
||||
// Ensuring only one instance of program based on http://stackoverflow.com/questions/229565/what-is-a-good-pattern-for-using-a-global-mutex-in-c
|
||||
using (Mutex mutex = new Mutex(false, "82E6AC09-0FEF-4390-AD9F-0DD3F5561EFC")) // Specific mutex required for installer
|
||||
{
|
||||
using (Mutex mutex = new Mutex(false, "82E6AC09-0FEF-4390-AD9F-0DD3F5561EFC")) // Required for installer
|
||||
bool hasHandle = false;
|
||||
try
|
||||
{
|
||||
Run();
|
||||
}
|
||||
try
|
||||
{
|
||||
hasHandle = mutex.WaitOne(100, false);
|
||||
if (hasHandle == false && !IsMultiInstance)
|
||||
{
|
||||
ApplicationInstanceManager.CreateMultipleInstance(SingleInstanceCallback, args);
|
||||
}
|
||||
}
|
||||
catch (AbandonedMutexException)
|
||||
{
|
||||
// Log the mutex was abandoned in another process, it will still get acquired
|
||||
DebugHelper.WriteLine("Single instance mutex found abandoned from another process");
|
||||
hasHandle = true;
|
||||
}
|
||||
|
||||
if (restarting)
|
||||
ApplicationInstanceManager.CreateFirstInstance(SingleInstanceCallback);
|
||||
Run();
|
||||
|
||||
if (restarting)
|
||||
{
|
||||
Process.Start(Application.ExecutablePath);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Process.Start(Application.ExecutablePath);
|
||||
if (hasHandle)
|
||||
mutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue