25
25
26
26
namespace Flow . Launcher
27
27
{
28
- public partial class App : IDisposable , ISingleInstanceApp
28
+ public partial class App : IAsyncDisposable , ISingleInstanceApp
29
29
{
30
+ #region Public Properties
31
+
30
32
public static IPublicAPI API { get ; private set ; }
33
+ public static CancellationTokenSource NativeThreadCTS { get ; private set ; }
34
+
35
+ #endregion
36
+
37
+ #region Private Fields
38
+
31
39
private static bool _disposed ;
40
+ private MainWindow _mainWindow ;
41
+ private readonly MainViewModel _mainVM ;
32
42
private readonly Settings _settings ;
33
43
44
+ // To prevent two disposals running at the same time.
45
+ private static readonly object _disposingLock = new ( ) ;
46
+
47
+ #endregion
48
+
49
+ #region Constructor
50
+
34
51
public App ( )
35
52
{
36
53
// Initialize settings
@@ -78,34 +95,47 @@ public App()
78
95
{
79
96
API = Ioc . Default . GetRequiredService < IPublicAPI > ( ) ;
80
97
_settings . Initialize ( ) ;
98
+ _mainVM = Ioc . Default . GetRequiredService < MainViewModel > ( ) ;
81
99
}
82
100
catch ( Exception e )
83
101
{
84
102
ShowErrorMsgBoxAndFailFast ( "Cannot initialize api and settings, please open new issue in Flow.Launcher" , e ) ;
85
103
return ;
86
104
}
87
- }
88
105
89
- private static void ShowErrorMsgBoxAndFailFast ( string message , Exception e )
90
- {
91
- // Firstly show users the message
92
- MessageBox . Show ( e . ToString ( ) , message , MessageBoxButton . OK , MessageBoxImage . Error ) ;
106
+ // Local function
107
+ static void ShowErrorMsgBoxAndFailFast ( string message , Exception e )
108
+ {
109
+ // Firstly show users the message
110
+ MessageBox . Show ( e . ToString ( ) , message , MessageBoxButton . OK , MessageBoxImage . Error ) ;
93
111
94
- // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
95
- Environment . FailFast ( message , e ) ;
112
+ // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
113
+ Environment . FailFast ( message , e ) ;
114
+ }
96
115
}
97
116
117
+ #endregion
118
+
119
+ #region Main
120
+
98
121
[ STAThread ]
99
122
public static void Main ( )
100
123
{
124
+ NativeThreadCTS = new CancellationTokenSource ( ) ;
125
+
101
126
if ( SingleInstance < App > . InitializeAsFirstInstance ( ) )
102
127
{
103
- using var application = new App ( ) ;
128
+ var application = new App ( ) ;
104
129
application . InitializeComponent ( ) ;
105
130
application . Run ( ) ;
131
+ application . DisposeAsync ( ) . AsTask ( ) . GetAwaiter ( ) . GetResult ( ) ;
106
132
}
107
133
}
108
134
135
+ #endregion
136
+
137
+ #region App Events
138
+
109
139
#pragma warning disable VSTHRD100 // Avoid async void methods
110
140
111
141
private async void OnStartup ( object sender , StartupEventArgs e )
@@ -136,11 +166,11 @@ await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
136
166
await PluginManager . InitializePluginsAsync ( ) ;
137
167
await imageLoadertask ;
138
168
139
- var window = new MainWindow ( ) ;
169
+ _mainWindow = new MainWindow ( ) ;
140
170
141
171
Log . Info ( $ "|App.OnStartup|Dependencies Info:{ ErrorReporting . DependenciesInfo ( ) } ") ;
142
172
143
- Current . MainWindow = window ;
173
+ Current . MainWindow = _mainWindow ;
144
174
Current . MainWindow . Title = Constant . FlowLauncher ;
145
175
146
176
HotKeyMapper . Initialize ( ) ;
@@ -157,8 +187,7 @@ await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
157
187
AutoUpdates ( ) ;
158
188
159
189
API . SaveAppAllSettings ( ) ;
160
- Log . Info (
161
- "|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- " ) ;
190
+ Log . Info ( "|App.OnStartup|End Flow Launcher startup ----------------------------------------------------" ) ;
162
191
} ) ;
163
192
}
164
193
@@ -191,7 +220,6 @@ private void AutoStartup()
191
220
}
192
221
}
193
222
194
- //[Conditional("RELEASE")]
195
223
private void AutoUpdates ( )
196
224
{
197
225
_ = Task . Run ( async ( ) =>
@@ -209,11 +237,30 @@ private void AutoUpdates()
209
237
} ) ;
210
238
}
211
239
240
+ #endregion
241
+
242
+ #region Register Events
243
+
212
244
private void RegisterExitEvents ( )
213
245
{
214
- AppDomain . CurrentDomain . ProcessExit += ( s , e ) => Dispose ( ) ;
215
- Current . Exit += ( s , e ) => Dispose ( ) ;
216
- Current . SessionEnding += ( s , e ) => Dispose ( ) ;
246
+ AppDomain . CurrentDomain . ProcessExit += ( s , e ) =>
247
+ {
248
+ Log . Info ( "|App.RegisterExitEvents|Process Exit" ) ;
249
+ _ = DisposeAsync ( ) ;
250
+ } ;
251
+
252
+ Current . Exit += ( s , e ) =>
253
+ {
254
+ NativeThreadCTS . Cancel ( ) ;
255
+ Log . Info ( "|App.RegisterExitEvents|Application Exit" ) ;
256
+ _ = DisposeAsync ( ) ;
257
+ } ;
258
+
259
+ Current . SessionEnding += ( s , e ) =>
260
+ {
261
+ Log . Info ( "|App.RegisterExitEvents|Session Ending" ) ;
262
+ _ = DisposeAsync ( ) ;
263
+ } ;
217
264
}
218
265
219
266
/// <summary>
@@ -234,20 +281,62 @@ private static void RegisterAppDomainExceptions()
234
281
AppDomain . CurrentDomain . UnhandledException += ErrorReporting . UnhandledExceptionHandle ;
235
282
}
236
283
237
- public void Dispose ( )
284
+ #endregion
285
+
286
+ #region IAsyncDisposable
287
+
288
+ protected virtual async ValueTask DisposeAsync ( bool disposing )
238
289
{
239
- // if sessionending is called, exit proverbially be called when log off / shutdown
240
- // but if sessionending is not called, exit won't be called when log off / shutdown
241
- if ( ! _disposed )
290
+ // Prevent two disposes at the same time.
291
+ lock ( _disposingLock )
242
292
{
243
- API . SaveAppAllSettings ( ) ;
293
+ if ( ! disposing )
294
+ {
295
+ return ;
296
+ }
297
+
298
+ if ( _disposed )
299
+ {
300
+ return ;
301
+ }
302
+
244
303
_disposed = true ;
245
304
}
305
+
306
+ await Stopwatch . NormalAsync ( "|App.Dispose|Dispose cost" , async ( ) =>
307
+ {
308
+ Log . Info ( "|App.Dispose|Begin Flow Launcher dispose ----------------------------------------------------" ) ;
309
+
310
+ if ( disposing )
311
+ {
312
+ API ? . SaveAppAllSettings ( ) ;
313
+ await PluginManager . DisposePluginsAsync ( ) ;
314
+
315
+ // Dispose needs to be called on the main Windows thread, since some resources owned by the thread need to be disposed.
316
+ await _mainWindow ? . Dispatcher . InvokeAsync ( DisposeAsync ) ;
317
+ _mainVM ? . Dispose ( ) ;
318
+ }
319
+
320
+ Log . Info ( "|App.Dispose|End Flow Launcher dispose ----------------------------------------------------" ) ;
321
+ } ) ;
246
322
}
247
323
324
+ public async ValueTask DisposeAsync ( )
325
+ {
326
+ // Do not change this code. Put cleanup code in 'DisposeAsync(bool disposing)' method
327
+ await DisposeAsync ( disposing : true ) ;
328
+ GC . SuppressFinalize ( this ) ;
329
+ }
330
+
331
+ #endregion
332
+
333
+ #region ISingleInstanceApp
334
+
248
335
public void OnSecondAppStarted ( )
249
336
{
250
337
Ioc . Default . GetRequiredService < MainViewModel > ( ) . Show ( ) ;
251
338
}
339
+
340
+ #endregion
252
341
}
253
342
}
0 commit comments