Improve performance, thread safety, and resource handling

Enhanced monitor initialization with parallelism in `MonitorManager.cs` for better performance. Added cancellation support to `NativeEventWaiter.cs` with `CancellationToken` and timeout handling. Introduced thread safety in `PhysicalMonitorHandleManager.cs` using locks to prevent race conditions.

Updated `PowerDisplayViewModel.cs` to include proper resource cleanup with `CancellationTokenSource` and improved memory management. Added necessary namespaces for threading and asynchronous operations. General code improvements for readability, maintainability, and reliability.
This commit is contained in:
Yu Leng
2025-11-19 15:08:00 +08:00
parent f10c9f49e9
commit ad83b5e67f
4 changed files with 54 additions and 28 deletions

View File

@@ -104,9 +104,12 @@ namespace PowerDisplay.Core
var results = await Task.WhenAll(discoveryTasks);
// Collect all discovered monitors
var allMonitors = new List<Monitor>();
foreach (var (controller, monitors) in results)
{
foreach (var monitor in monitors)
// Initialize monitors in parallel
var initTasks = monitors.Select(async monitor =>
{
// Verify if monitor can be controlled
if (await controller.CanControlMonitorAsync(monitor, cancellationToken))
@@ -165,9 +168,14 @@ namespace PowerDisplay.Core
}
}
newMonitors.Add(monitor);
}
return monitor;
}
return null;
});
var initializedMonitors = await Task.WhenAll(initTasks);
newMonitors.AddRange(initializedMonitors.Where(m => m != null));
}
// Update monitor list

View File

@@ -19,15 +19,16 @@ namespace PowerDisplay.Helpers
/// </summary>
/// <param name="eventName">Name of the Windows Event to wait for</param>
/// <param name="callback">Callback to invoke when event is signaled</param>
public static void WaitForEventLoop(string eventName, Action callback)
/// <param name="cancellationToken">Token to cancel the wait loop</param>
public static void WaitForEventLoop(string eventName, Action callback, CancellationToken cancellationToken)
{
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
var t = new Thread(() =>
{
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
while (true)
using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
while (!cancellationToken.IsCancellationRequested)
{
if (eventHandle.WaitOne())
if (eventHandle.WaitOne(500))
{
dispatcherQueue.TryEnqueue(() => callback());
}

View File

@@ -17,12 +17,15 @@ namespace PowerDisplay.Native.DDC
{
// Mapping: deviceKey -> physical handle
private readonly Dictionary<string, IntPtr> _deviceKeyToHandleMap = new();
private readonly object _lock = new();
private bool _disposed;
/// <summary>
/// Get physical handle for monitor using stable deviceKey
/// </summary>
public IntPtr GetPhysicalHandle(Monitor monitor)
{
lock (_lock)
{
// Primary lookup: use stable deviceKey from EnumDisplayDevices
if (!string.IsNullOrEmpty(monitor.DeviceKey) &&
@@ -30,6 +33,7 @@ namespace PowerDisplay.Native.DDC
{
return handle;
}
}
// Fallback: use direct handle from monitor object
if (monitor.Handle != IntPtr.Zero)
@@ -51,6 +55,8 @@ namespace PowerDisplay.Native.DDC
return (newHandle, false);
}
lock (_lock)
{
// Try to reuse existing handle if it's still valid
if (_deviceKeyToHandleMap.TryGetValue(deviceKey, out var existingHandle) &&
existingHandle != IntPtr.Zero &&
@@ -64,6 +70,7 @@ namespace PowerDisplay.Native.DDC
return (existingHandle, true);
}
}
return (newHandle, false);
}
@@ -72,6 +79,8 @@ namespace PowerDisplay.Native.DDC
/// Update the handle mapping with new handles
/// </summary>
public void UpdateHandleMap(Dictionary<string, IntPtr> newHandleMap)
{
lock (_lock)
{
// Clean up unused handles before updating
CleanupUnusedHandles(newHandleMap);
@@ -83,6 +92,7 @@ namespace PowerDisplay.Native.DDC
_deviceKeyToHandleMap[kvp.Key] = kvp.Value;
}
}
}
/// <summary>
/// Clean up handles that are no longer in use

View File

@@ -10,6 +10,7 @@ using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -72,7 +73,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
Logger.LogInfo("Received refresh monitors event from PowerDisplay.exe");
ReloadMonitorsFromSettings();
});
},
_cancellationTokenSource.Token);
}
private void InitializeEnabledValue()
@@ -284,6 +286,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_monitors.CollectionChanged -= Monitors_CollectionChanged;
}
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
GC.SuppressFinalize(this);
base.Dispose();
}
@@ -447,6 +453,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private PowerDisplaySettings _settings;
private ObservableCollection<MonitorInfo> _monitors;
private bool _hasMonitors;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
public void RefreshEnabledState()
{