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); var results = await Task.WhenAll(discoveryTasks);
// Collect all discovered monitors // Collect all discovered monitors
var allMonitors = new List<Monitor>();
foreach (var (controller, monitors) in results) 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 // Verify if monitor can be controlled
if (await controller.CanControlMonitorAsync(monitor, cancellationToken)) 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 // Update monitor list

View File

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

View File

@@ -17,6 +17,7 @@ namespace PowerDisplay.Native.DDC
{ {
// Mapping: deviceKey -> physical handle // Mapping: deviceKey -> physical handle
private readonly Dictionary<string, IntPtr> _deviceKeyToHandleMap = new(); private readonly Dictionary<string, IntPtr> _deviceKeyToHandleMap = new();
private readonly object _lock = new();
private bool _disposed; private bool _disposed;
/// <summary> /// <summary>
@@ -24,11 +25,14 @@ namespace PowerDisplay.Native.DDC
/// </summary> /// </summary>
public IntPtr GetPhysicalHandle(Monitor monitor) public IntPtr GetPhysicalHandle(Monitor monitor)
{ {
// Primary lookup: use stable deviceKey from EnumDisplayDevices lock (_lock)
if (!string.IsNullOrEmpty(monitor.DeviceKey) &&
_deviceKeyToHandleMap.TryGetValue(monitor.DeviceKey, out var handle))
{ {
return handle; // Primary lookup: use stable deviceKey from EnumDisplayDevices
if (!string.IsNullOrEmpty(monitor.DeviceKey) &&
_deviceKeyToHandleMap.TryGetValue(monitor.DeviceKey, out var handle))
{
return handle;
}
} }
// Fallback: use direct handle from monitor object // Fallback: use direct handle from monitor object
@@ -51,18 +55,21 @@ namespace PowerDisplay.Native.DDC
return (newHandle, false); return (newHandle, false);
} }
// Try to reuse existing handle if it's still valid lock (_lock)
if (_deviceKeyToHandleMap.TryGetValue(deviceKey, out var existingHandle) &&
existingHandle != IntPtr.Zero &&
DdcCiNative.ValidateDdcCiConnection(existingHandle))
{ {
// Destroy the newly created handle since we're using the old one // Try to reuse existing handle if it's still valid
if (newHandle != existingHandle && newHandle != IntPtr.Zero) if (_deviceKeyToHandleMap.TryGetValue(deviceKey, out var existingHandle) &&
existingHandle != IntPtr.Zero &&
DdcCiNative.ValidateDdcCiConnection(existingHandle))
{ {
DestroyPhysicalMonitor(newHandle); // Destroy the newly created handle since we're using the old one
} if (newHandle != existingHandle && newHandle != IntPtr.Zero)
{
DestroyPhysicalMonitor(newHandle);
}
return (existingHandle, true); return (existingHandle, true);
}
} }
return (newHandle, false); return (newHandle, false);
@@ -73,14 +80,17 @@ namespace PowerDisplay.Native.DDC
/// </summary> /// </summary>
public void UpdateHandleMap(Dictionary<string, IntPtr> newHandleMap) public void UpdateHandleMap(Dictionary<string, IntPtr> newHandleMap)
{ {
// Clean up unused handles before updating lock (_lock)
CleanupUnusedHandles(newHandleMap);
// Update the device key map
_deviceKeyToHandleMap.Clear();
foreach (var kvp in newHandleMap)
{ {
_deviceKeyToHandleMap[kvp.Key] = kvp.Value; // Clean up unused handles before updating
CleanupUnusedHandles(newHandleMap);
// Update the device key map
_deviceKeyToHandleMap.Clear();
foreach (var kvp in newHandleMap)
{
_deviceKeyToHandleMap[kvp.Key] = kvp.Value;
}
} }
} }

View File

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