Skip to content

Commit e5ee856

Browse files
authored
Merge pull request #3367 from Jack251970/graceful_shutdown
Graceful shutdown
2 parents 298af4d + 1d1909c commit e5ee856

File tree

5 files changed

+247
-102
lines changed

5 files changed

+247
-102
lines changed

Flow.Launcher/App.xaml.cs

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,26 @@ namespace Flow.Launcher
2727
{
2828
public partial class App : IDisposable, ISingleInstanceApp
2929
{
30+
#region Public Properties
31+
3032
public static IPublicAPI API { get; private set; }
31-
private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
33+
34+
#endregion
35+
36+
#region Private Fields
37+
3238
private static bool _disposed;
39+
private MainWindow _mainWindow;
40+
private readonly MainViewModel _mainVM;
3341
private readonly Settings _settings;
3442

43+
// To prevent two disposals running at the same time.
44+
private static readonly object _disposingLock = new();
45+
46+
#endregion
47+
48+
#region Constructor
49+
3550
public App()
3651
{
3752
// Initialize settings
@@ -79,34 +94,44 @@ public App()
7994
{
8095
API = Ioc.Default.GetRequiredService<IPublicAPI>();
8196
_settings.Initialize();
97+
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
8298
}
8399
catch (Exception e)
84100
{
85101
ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e);
86102
return;
87103
}
88-
}
89104

90-
private static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
91-
{
92-
// Firstly show users the message
93-
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
105+
// Local function
106+
static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
107+
{
108+
// Firstly show users the message
109+
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
94110

95-
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
96-
Environment.FailFast(message, e);
111+
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
112+
Environment.FailFast(message, e);
113+
}
97114
}
98115

116+
#endregion
117+
118+
#region Main
119+
99120
[STAThread]
100121
public static void Main()
101122
{
102-
if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
123+
if (SingleInstance<App>.InitializeAsFirstInstance())
103124
{
104125
using var application = new App();
105126
application.InitializeComponent();
106127
application.Run();
107128
}
108129
}
109130

131+
#endregion
132+
133+
#region App Events
134+
110135
#pragma warning disable VSTHRD100 // Avoid async void methods
111136

112137
private async void OnStartup(object sender, StartupEventArgs e)
@@ -142,11 +167,11 @@ await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
142167

143168
await imageLoadertask;
144169

145-
var window = new MainWindow();
170+
_mainWindow = new MainWindow();
146171

147172
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
148173

149-
Current.MainWindow = window;
174+
Current.MainWindow = _mainWindow;
150175
Current.MainWindow.Title = Constant.FlowLauncher;
151176

152177
HotKeyMapper.Initialize();
@@ -163,8 +188,7 @@ await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
163188
AutoUpdates();
164189

165190
API.SaveAppAllSettings();
166-
Log.Info(
167-
"|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
191+
Log.Info("|App.OnStartup|End Flow Launcher startup ----------------------------------------------------");
168192
});
169193
}
170194

@@ -197,7 +221,6 @@ private void AutoStartup()
197221
}
198222
}
199223

200-
//[Conditional("RELEASE")]
201224
private void AutoUpdates()
202225
{
203226
_ = Task.Run(async () =>
@@ -215,11 +238,29 @@ private void AutoUpdates()
215238
});
216239
}
217240

241+
#endregion
242+
243+
#region Register Events
244+
218245
private void RegisterExitEvents()
219246
{
220-
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
221-
Current.Exit += (s, e) => Dispose();
222-
Current.SessionEnding += (s, e) => Dispose();
247+
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
248+
{
249+
Log.Info("|App.RegisterExitEvents|Process Exit");
250+
Dispose();
251+
};
252+
253+
Current.Exit += (s, e) =>
254+
{
255+
Log.Info("|App.RegisterExitEvents|Application Exit");
256+
Dispose();
257+
};
258+
259+
Current.SessionEnding += (s, e) =>
260+
{
261+
Log.Info("|App.RegisterExitEvents|Session Ending");
262+
Dispose();
263+
};
223264
}
224265

225266
/// <summary>
@@ -240,20 +281,60 @@ private static void RegisterAppDomainExceptions()
240281
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
241282
}
242283

243-
public void Dispose()
284+
#endregion
285+
286+
#region IDisposable
287+
288+
protected virtual void Dispose(bool disposing)
244289
{
245-
// if sessionending is called, exit proverbially be called when log off / shutdown
246-
// but if sessionending is not called, exit won't be called when log off / shutdown
247-
if (!_disposed)
290+
// Prevent two disposes at the same time.
291+
lock (_disposingLock)
248292
{
249-
API.SaveAppAllSettings();
293+
if (!disposing)
294+
{
295+
return;
296+
}
297+
298+
if (_disposed)
299+
{
300+
return;
301+
}
302+
250303
_disposed = true;
251304
}
305+
306+
Stopwatch.Normal("|App.Dispose|Dispose cost", () =>
307+
{
308+
Log.Info("|App.Dispose|Begin Flow Launcher dispose ----------------------------------------------------");
309+
310+
if (disposing)
311+
{
312+
// Dispose needs to be called on the main Windows thread,
313+
// since some resources owned by the thread need to be disposed.
314+
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
315+
_mainVM?.Dispose();
316+
}
317+
318+
Log.Info("|App.Dispose|End Flow Launcher dispose ----------------------------------------------------");
319+
});
252320
}
253321

322+
public void Dispose()
323+
{
324+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
325+
Dispose(disposing: true);
326+
GC.SuppressFinalize(this);
327+
}
328+
329+
#endregion
330+
331+
#region ISingleInstanceApp
332+
254333
public void OnSecondAppStarted()
255334
{
256335
Ioc.Default.GetRequiredService<MainViewModel>().Show();
257336
}
337+
338+
#endregion
258339
}
259340
}

Flow.Launcher/Helper/SingleInstance.cs

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
// modified to allow single instace restart
99
namespace Flow.Launcher.Helper
1010
{
11-
public interface ISingleInstanceApp
12-
{
13-
void OnSecondAppStarted();
14-
}
11+
public interface ISingleInstanceApp
12+
{
13+
void OnSecondAppStarted();
14+
}
1515

1616
/// <summary>
1717
/// This class checks to make sure that only one instance of
@@ -24,9 +24,7 @@ public interface ISingleInstanceApp
2424
/// running as Administrator, can activate it with command line arguments.
2525
/// For most apps, this will not be much of an issue.
2626
/// </remarks>
27-
public static class SingleInstance<TApplication>
28-
where TApplication: Application , ISingleInstanceApp
29-
27+
public static class SingleInstance<TApplication> where TApplication : Application, ISingleInstanceApp
3028
{
3129
#region Private Fields
3230

@@ -39,11 +37,12 @@ public static class SingleInstance<TApplication>
3937
/// Suffix to the channel name.
4038
/// </summary>
4139
private const string ChannelNameSuffix = "SingeInstanceIPCChannel";
40+
private const string InstanceMutexName = "Flow.Launcher_Unique_Application_Mutex";
4241

4342
/// <summary>
4443
/// Application mutex.
4544
/// </summary>
46-
internal static Mutex singleInstanceMutex;
45+
internal static Mutex SingleInstanceMutex { get; set; }
4746

4847
#endregion
4948

@@ -54,24 +53,23 @@ public static class SingleInstance<TApplication>
5453
/// If not, activates the first instance.
5554
/// </summary>
5655
/// <returns>True if this is the first instance of the application.</returns>
57-
public static bool InitializeAsFirstInstance( string uniqueName )
56+
public static bool InitializeAsFirstInstance()
5857
{
5958
// Build unique application Id and the IPC channel name.
60-
string applicationIdentifier = uniqueName + Environment.UserName;
59+
string applicationIdentifier = InstanceMutexName + Environment.UserName;
6160

62-
string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
61+
string channelName = string.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
6362

6463
// Create mutex based on unique application Id to check if this is the first instance of the application.
65-
bool firstInstance;
66-
singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance);
64+
SingleInstanceMutex = new Mutex(true, applicationIdentifier, out var firstInstance);
6765
if (firstInstance)
6866
{
69-
_ = CreateRemoteService(channelName);
67+
_ = CreateRemoteServiceAsync(channelName);
7068
return true;
7169
}
7270
else
7371
{
74-
_ = SignalFirstInstance(channelName);
72+
_ = SignalFirstInstanceAsync(channelName);
7573
return false;
7674
}
7775
}
@@ -81,7 +79,7 @@ public static bool InitializeAsFirstInstance( string uniqueName )
8179
/// </summary>
8280
public static void Cleanup()
8381
{
84-
singleInstanceMutex?.ReleaseMutex();
82+
SingleInstanceMutex?.ReleaseMutex();
8583
}
8684

8785
#endregion
@@ -93,22 +91,19 @@ public static void Cleanup()
9391
/// Once receives signal from client, will activate first instance.
9492
/// </summary>
9593
/// <param name="channelName">Application's IPC channel name.</param>
96-
private static async Task CreateRemoteService(string channelName)
94+
private static async Task CreateRemoteServiceAsync(string channelName)
9795
{
98-
using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In))
96+
using NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In);
97+
while (true)
9998
{
100-
while(true)
101-
{
102-
// Wait for connection to the pipe
103-
await pipeServer.WaitForConnectionAsync();
104-
if (Application.Current != null)
105-
{
106-
// Do an asynchronous call to ActivateFirstInstance function
107-
Application.Current.Dispatcher.Invoke(ActivateFirstInstance);
108-
}
109-
// Disconect client
110-
pipeServer.Disconnect();
111-
}
99+
// Wait for connection to the pipe
100+
await pipeServer.WaitForConnectionAsync();
101+
102+
// Do an asynchronous call to ActivateFirstInstance function
103+
Application.Current?.Dispatcher.Invoke(ActivateFirstInstance);
104+
105+
// Disconect client
106+
pipeServer.Disconnect();
112107
}
113108
}
114109

@@ -119,25 +114,13 @@ private static async Task CreateRemoteService(string channelName)
119114
/// <param name="args">
120115
/// Command line arguments for the second instance, passed to the first instance to take appropriate action.
121116
/// </param>
122-
private static async Task SignalFirstInstance(string channelName)
117+
private static async Task SignalFirstInstanceAsync(string channelName)
123118
{
124119
// Create a client pipe connected to server
125-
using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out))
126-
{
127-
// Connect to the available pipe
128-
await pipeClient.ConnectAsync(0);
129-
}
130-
}
120+
using NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out);
131121

132-
/// <summary>
133-
/// Callback for activating first instance of the application.
134-
/// </summary>
135-
/// <param name="arg">Callback argument.</param>
136-
/// <returns>Always null.</returns>
137-
private static object ActivateFirstInstanceCallback(object o)
138-
{
139-
ActivateFirstInstance();
140-
return null;
122+
// Connect to the available pipe
123+
await pipeClient.ConnectAsync(0);
141124
}
142125

143126
/// <summary>

Flow.Launcher/MainWindow.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
AllowDrop="True"
1818
AllowsTransparency="True"
1919
Background="Transparent"
20+
Closed="OnClosed"
2021
Closing="OnClosing"
2122
Deactivated="OnDeactivated"
2223
Icon="Images/app.png"

0 commit comments

Comments
 (0)