mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Gracefully shutdown all threads when exiting
This commit is contained in:
parent
1b066a5724
commit
783cef6814
4 changed files with 187 additions and 34 deletions
|
|
@ -25,12 +25,29 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
|||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class App : IDisposable, ISingleInstanceApp
|
||||
public partial class App : IAsyncDisposable, ISingleInstanceApp
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public static IPublicAPI API { get; private set; }
|
||||
public static CancellationTokenSource NativeThreadCTS { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private static bool _disposed;
|
||||
private MainWindow _mainWindow;
|
||||
private readonly MainViewModel _mainVM;
|
||||
private readonly Settings _settings;
|
||||
|
||||
// To prevent two disposals running at the same time.
|
||||
private static readonly object _disposingLock = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public App()
|
||||
{
|
||||
// Initialize settings
|
||||
|
|
@ -78,34 +95,47 @@ namespace Flow.Launcher
|
|||
{
|
||||
API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
_settings.Initialize();
|
||||
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Local function
|
||||
static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
|
||||
{
|
||||
// Firstly show users the message
|
||||
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
|
||||
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
|
||||
Environment.FailFast(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
|
||||
{
|
||||
// Firstly show users the message
|
||||
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
#endregion
|
||||
|
||||
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
|
||||
Environment.FailFast(message, e);
|
||||
}
|
||||
#region Main
|
||||
|
||||
[STAThread]
|
||||
public static void Main()
|
||||
{
|
||||
NativeThreadCTS = new CancellationTokenSource();
|
||||
|
||||
if (SingleInstance<App>.InitializeAsFirstInstance())
|
||||
{
|
||||
using var application = new App();
|
||||
var application = new App();
|
||||
application.InitializeComponent();
|
||||
application.Run();
|
||||
application.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region App Events
|
||||
|
||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||
|
||||
private async void OnStartup(object sender, StartupEventArgs e)
|
||||
|
|
@ -136,11 +166,11 @@ namespace Flow.Launcher
|
|||
await PluginManager.InitializePluginsAsync();
|
||||
await imageLoadertask;
|
||||
|
||||
var window = new MainWindow();
|
||||
_mainWindow = new MainWindow();
|
||||
|
||||
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
|
||||
|
||||
Current.MainWindow = window;
|
||||
Current.MainWindow = _mainWindow;
|
||||
Current.MainWindow.Title = Constant.FlowLauncher;
|
||||
|
||||
HotKeyMapper.Initialize();
|
||||
|
|
@ -157,8 +187,7 @@ namespace Flow.Launcher
|
|||
AutoUpdates();
|
||||
|
||||
API.SaveAppAllSettings();
|
||||
Log.Info(
|
||||
"|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
|
||||
Log.Info("|App.OnStartup|End Flow Launcher startup ----------------------------------------------------");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +220,6 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
|
||||
//[Conditional("RELEASE")]
|
||||
private void AutoUpdates()
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
|
|
@ -209,11 +237,30 @@ namespace Flow.Launcher
|
|||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Register Events
|
||||
|
||||
private void RegisterExitEvents()
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
|
||||
Current.Exit += (s, e) => Dispose();
|
||||
Current.SessionEnding += (s, e) => Dispose();
|
||||
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
|
||||
{
|
||||
Log.Info("|App.RegisterExitEvents|Process Exit");
|
||||
_ = DisposeAsync();
|
||||
};
|
||||
|
||||
Current.Exit += (s, e) =>
|
||||
{
|
||||
NativeThreadCTS.Cancel();
|
||||
Log.Info("|App.RegisterExitEvents|Application Exit");
|
||||
_ = DisposeAsync();
|
||||
};
|
||||
|
||||
Current.SessionEnding += (s, e) =>
|
||||
{
|
||||
Log.Info("|App.RegisterExitEvents|Session Ending");
|
||||
_ = DisposeAsync();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -234,20 +281,62 @@ namespace Flow.Launcher
|
|||
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
#endregion
|
||||
|
||||
#region IAsyncDisposable
|
||||
|
||||
protected virtual async ValueTask DisposeAsync(bool disposing)
|
||||
{
|
||||
// if sessionending is called, exit proverbially be called when log off / shutdown
|
||||
// but if sessionending is not called, exit won't be called when log off / shutdown
|
||||
if (!_disposed)
|
||||
// Prevent two disposes at the same time.
|
||||
lock (_disposingLock)
|
||||
{
|
||||
API.SaveAppAllSettings();
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
await Stopwatch.NormalAsync("|App.Dispose|Dispose cost", async () =>
|
||||
{
|
||||
Log.Info("|App.Dispose|Begin Flow Launcher dispose ----------------------------------------------------");
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
API?.SaveAppAllSettings();
|
||||
await PluginManager.DisposePluginsAsync();
|
||||
|
||||
// Dispose needs to be called on the main Windows thread, since some resources owned by the thread need to be disposed.
|
||||
await _mainWindow?.Dispatcher.InvokeAsync(DisposeAsync);
|
||||
_mainVM?.Dispose();
|
||||
}
|
||||
|
||||
Log.Info("|App.Dispose|End Flow Launcher dispose ----------------------------------------------------");
|
||||
});
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'DisposeAsync(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ISingleInstanceApp
|
||||
|
||||
public void OnSecondAppStarted()
|
||||
{
|
||||
Ioc.Default.GetRequiredService<MainViewModel>().Show();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
AllowDrop="True"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Closed="OnClosed"
|
||||
Closing="OnClosing"
|
||||
Deactivated="OnDeactivated"
|
||||
Icon="Images/app.png"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ using Screen = System.Windows.Forms.Screen;
|
|||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class MainWindow
|
||||
public partial class MainWindow : IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
|
|
@ -50,13 +50,17 @@ namespace Flow.Launcher
|
|||
private SoundPlayer animationSoundWPF;
|
||||
|
||||
// Window WndProc
|
||||
private HwndSource _hwndSource;
|
||||
private int _initialWidth;
|
||||
private int _initialHeight;
|
||||
|
||||
// Window Animation
|
||||
private const double DefaultRightMargin = 66; //* this value from base.xaml
|
||||
private bool _animating;
|
||||
private bool _isClockPanelAnimating = false; // 애니메이션 실행 중인지 여부
|
||||
private bool _isClockPanelAnimating = false;
|
||||
|
||||
// IDisposable
|
||||
private bool _disposedValue = false;
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -85,8 +89,8 @@ namespace Flow.Launcher
|
|||
private void OnSourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
var handle = Win32Helper.GetWindowHandle(this, true);
|
||||
var win = HwndSource.FromHwnd(handle);
|
||||
win.AddHook(WndProc);
|
||||
_hwndSource = HwndSource.FromHwnd(handle);
|
||||
_hwndSource.AddHook(WndProc);
|
||||
Win32Helper.HideFromAltTab(this);
|
||||
Win32Helper.DisableControlBox(this);
|
||||
}
|
||||
|
|
@ -227,20 +231,31 @@ namespace Flow.Launcher
|
|||
.AddValueChanged(History, (s, e) => UpdateClockPanelVisibility());
|
||||
}
|
||||
|
||||
private async void OnClosing(object sender, CancelEventArgs e)
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_viewModel.Save();
|
||||
_notifyIcon.Visible = false;
|
||||
App.API.SaveAppAllSettings();
|
||||
e.Cancel = true;
|
||||
await PluginManager.DisposePluginsAsync();
|
||||
Notification.Uninstall();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void OnClosed(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_hwndSource.RemoveHook(WndProc);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
_hwndSource = null;
|
||||
}
|
||||
|
||||
private void OnLocationChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_animating)
|
||||
return;
|
||||
if (_animating) return;
|
||||
|
||||
if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation)
|
||||
{
|
||||
_settings.WindowLeft = Left;
|
||||
|
|
@ -990,5 +1005,29 @@ namespace Flow.Launcher
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_hwndSource?.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ using Microsoft.VisualStudio.Threading;
|
|||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
public partial class MainViewModel : BaseModel, ISavable
|
||||
public partial class MainViewModel : BaseModel, ISavable, IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
|
|
@ -1542,5 +1542,29 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_updateSource?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue