Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.48.30] - 2026-05-18

### Fixed
- **ViewModelBase** — added `InitializeAsync` helper method that wraps
fire-and-forget async calls with structured error handling. Exceptions
from async initialization are now caught and logged via Serilog instead
of becoming unobserved task exceptions (CQ-M3).
- **12 ViewModels** — replaced `_ = InitAsync()` fire-and-forget pattern
with `InitializeAsync(InitAsync)` in: AboutViewModel, BatteryHealthViewModel,
CleanupViewModel, DashboardViewModel, DeepCleanupViewModel,
PerformanceViewModel, ProcessManagerViewModel, ServicesViewModel,
SpeedTestViewModel, StartupViewModel, SystemHealthViewModel,
WindowsUpdateViewModel.

## [0.48.29] - 2026-05-18

### Changed
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/AboutViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public AboutViewModel() : this(new UpdateService()) { }
public AboutViewModel(UpdateService updates)
{
_updates = updates;
_ = InitAsync();
InitializeAsync(InitAsync);
}

private async Task InitAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public partial class BatteryHealthViewModel : ViewModelBase

public BatteryHealthViewModel()
{
_ = InitAsync();
InitializeAsync(InitAsync);
}

private async Task InitAsync()
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/CleanupViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public CleanupViewModel(PowerShellRunner runner)
_runner.ProgressChanged += OnRunnerProgressChanged;
IsElevated = AdminHelper.IsElevated();

_ = InitAsync();
InitializeAsync(InitAsync);
}

private void OnRunnerLineReceived(PowerShellLine l) => Console.Append(l);
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/DashboardViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public DashboardViewModel(SystemInfoService sys, TuneUpService tuneUp, HealthSco
_tuneUp = tuneUp;
_healthScore = healthScore;
IsElevated = AdminHelper.IsElevated();
_ = LoadHealthScoreAsync();
InitializeAsync(LoadHealthScoreAsync);
}

// ── Health Score ───────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/DeepCleanupViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public partial class DeepCleanupViewModel : ViewModelBase

public DeepCleanupViewModel()
{
_ = InitAsync();
InitializeAsync(InitAsync);
}

private async Task InitAsync()
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/PerformanceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public partial class PerformanceViewModel : ViewModelBase
public PerformanceViewModel(PowerShellRunner ps)
{
_service = new PerformanceService(ps);
_ = InitAsync();
InitializeAsync(InitAsync);
}

private async Task InitAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public partial class ProcessManagerViewModel : ViewModelBase

public ProcessManagerViewModel()
{
_ = InitAsync();
InitializeAsync(InitAsync);
}

private async Task InitAsync()
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/ServicesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public partial class ServicesViewModel : ViewModelBase

public ServicesViewModel()
{
_ = InitAsync();
InitializeAsync(InitAsync);
}

private async Task InitAsync()
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/SpeedTestViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public partial class SpeedTestViewModel : ViewModelBase
public SpeedTestViewModel(NetworkSharedState shared)
{
Shared = shared;
_ = LoadHistoryAsync();
InitializeAsync(LoadHistoryAsync);
}

private async Task LoadHistoryAsync()
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/ViewModels/StartupViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public partial class StartupViewModel : ViewModelBase

public StartupViewModel()
{
_ = InitAsync();
InitializeAsync(InitAsync);
}

partial void OnHideWindowsEntriesChanged(bool value) => ApplyFilter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public SystemHealthViewModel(SystemInfoService sys)
IsElevated = AdminHelper.IsElevated();
_runner.LineReceived += OnRunnerLineReceived;

_ = InitAsync();
InitializeAsync(InitAsync);
}

private void OnRunnerLineReceived(PowerShellLine l) => Console.Append(l);
Expand Down
22 changes: 22 additions & 0 deletions SysManager/SysManager/ViewModels/ViewModelBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// License: MIT

using CommunityToolkit.Mvvm.ComponentModel;
using Serilog;

namespace SysManager.ViewModels;

Expand All @@ -15,6 +16,27 @@

private bool _disposed;

/// <summary>
/// Safely launches an async task from a constructor or non-async context.
/// Exceptions are caught and logged instead of becoming unobserved task
/// exceptions that could crash the application (CQ-M3).
/// </summary>
protected static async void InitializeAsync(Func<Task> asyncAction, [System.Runtime.CompilerServices.CallerMemberName] string callerName = "")
{
try
{
await asyncAction().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Expected during shutdown — no action needed.
}
catch (Exception ex)
{
Log.Error(ex, "Unhandled exception in async initialization of {Caller}", callerName);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
Comment on lines +34 to +37
}

/// <summary>
/// Override in derived classes to release managed resources
/// (CancellationTokenSources, event handlers, timers, etc.).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public WindowsUpdateViewModel(PowerShellRunner runner)
_runner.LineReceived += OnRunnerLineReceived;
_runner.ProgressChanged += OnRunnerProgressChanged;
IsElevated = AdminHelper.IsElevated();
_ = InitAsync();
InitializeAsync(InitAsync);
}

private void OnRunnerLineReceived(PowerShellLine l) => Console.Append(l);
Expand Down
Loading