From 890ca8990915f94de14d6c10c466365d94297ceb Mon Sep 17 00:00:00 2001 From: Yu Leng Date: Thu, 15 Jan 2026 16:10:07 +0800 Subject: [PATCH] fix(imageresizer): improve AI initialization and ensure UI thread handling --- src/modules/imageresizer/ui/App.xaml.cs | 26 +++------- .../Services/WinAiSuperResolutionService.cs | 52 ++++++++++++++++--- .../ui/ViewModels/InputViewModel.cs | 1 + 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/modules/imageresizer/ui/App.xaml.cs b/src/modules/imageresizer/ui/App.xaml.cs index 95e45b7f7b..3a70313608 100644 --- a/src/modules/imageresizer/ui/App.xaml.cs +++ b/src/modules/imageresizer/ui/App.xaml.cs @@ -107,33 +107,26 @@ namespace ImageResizer // Temporary workaround for issue #1273 WindowHelpers.BringToForeground(new System.Windows.Interop.WindowInteropHelper(mainWindow).Handle); - // Check AI availability on UI thread after window is loaded - // Windows AI APIs require proper COM apartment and SynchronizationContext + // Check AI availability after window is loaded + // WinAiSuperResolutionService handles UI thread dispatching internally if (!OSVersionHelper.IsWindows10()) { - mainWindow.Loaded += (s, args) => + mainWindow.Loaded += async (s, args) => { - // Use Dispatcher to ensure we're on the UI thread - mainWindow.Dispatcher.InvokeAsync(async () => - { - await InitializeAiOnUIThreadAsync(); - }); + await InitializeAiAsync(); }; } } /// - /// Initialize AI availability check on UI thread. - /// Windows AI APIs require proper COM apartment context. + /// Initialize AI availability check. + /// WinAiSuperResolutionService handles UI thread dispatching internally. /// - private static async System.Threading.Tasks.Task InitializeAiOnUIThreadAsync() + private static async System.Threading.Tasks.Task InitializeAiAsync() { try { - Logger.LogInfo($"Checking AI availability on UI thread (Thread: {System.Threading.Thread.CurrentThread.ManagedThreadId})"); - var readyState = Services.WinAiSuperResolutionService.GetModelReadyState(); - Logger.LogInfo($"ImageScaler.GetReadyState() returned: {readyState}"); switch (readyState) { @@ -144,21 +137,18 @@ namespace ImageResizer if (aiService != null) { ResizeBatch.SetAiSuperResolutionService(aiService); - Logger.LogInfo("AI Super Resolution service initialized successfully on UI thread."); } break; case Microsoft.Windows.AI.AIFeatureReadyState.NotReady: AiAvailabilityState = AiAvailabilityState.ModelNotReady; - Logger.LogInfo("AI model not ready, user can download via UI."); break; case Microsoft.Windows.AI.AIFeatureReadyState.DisabledByUser: case Microsoft.Windows.AI.AIFeatureReadyState.NotSupportedOnCurrentSystem: default: AiAvailabilityState = AiAvailabilityState.NotSupported; - Logger.LogInfo($"AI not supported: {readyState}"); break; } @@ -167,7 +157,7 @@ namespace ImageResizer } catch (Exception ex) { - Logger.LogError($"Failed to check AI availability on UI thread: {ex.Message}"); + Logger.LogError($"Failed to check AI availability: {ex.Message}"); AiAvailabilityState = AiAvailabilityState.NotSupported; AiInitializationCompleted?.Invoke(null, AiAvailabilityState); } diff --git a/src/modules/imageresizer/ui/Services/WinAiSuperResolutionService.cs b/src/modules/imageresizer/ui/Services/WinAiSuperResolutionService.cs index 4cd752184a..ce5f68086e 100644 --- a/src/modules/imageresizer/ui/Services/WinAiSuperResolutionService.cs +++ b/src/modules/imageresizer/ui/Services/WinAiSuperResolutionService.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using ManagedCommon; using Microsoft.Windows.AI; using Microsoft.Windows.AI.Imaging; using Windows.Graphics.Imaging; @@ -33,44 +34,78 @@ namespace ImageResizer.Services /// /// Async factory method to create and initialize WinAiSuperResolutionService. - /// Returns null if initialization fails. + /// Must be called on UI thread. Returns null if initialization fails. /// public static async Task CreateAsync() { + // Ensure we're on UI thread for Windows AI API calls + if (Application.Current?.Dispatcher != null && !Application.Current.Dispatcher.CheckAccess()) + { + return await Application.Current.Dispatcher.InvokeAsync(() => CreateAsync()).Task.Unwrap(); + } + try { + Logger.LogInfo($"ImageScaler.CreateAsync() called on thread {Thread.CurrentThread.ManagedThreadId}"); var imageScaler = await ImageScaler.CreateAsync(); if (imageScaler == null) { + Logger.LogWarning("ImageScaler.CreateAsync() returned null"); return null; } + Logger.LogInfo("ImageScaler created successfully"); return new WinAiSuperResolutionService(imageScaler); } - catch + catch (Exception ex) { + Logger.LogError($"ImageScaler.CreateAsync() failed: {ex.Message}"); return null; } } + /// + /// Get the ready state of the ImageScaler model. + /// Must be called on UI thread for accurate results. + /// public static AIFeatureReadyState GetModelReadyState() { + // Ensure we're on UI thread for Windows AI API calls + if (Application.Current?.Dispatcher != null && !Application.Current.Dispatcher.CheckAccess()) + { + return Application.Current.Dispatcher.Invoke(() => GetModelReadyState()); + } + try { - return ImageScaler.GetReadyState(); + Logger.LogInfo($"ImageScaler.GetReadyState() called on thread {Thread.CurrentThread.ManagedThreadId}"); + var state = ImageScaler.GetReadyState(); + Logger.LogInfo($"ImageScaler.GetReadyState() returned: {state}"); + return state; } - catch (Exception) + catch (Exception ex) { + Logger.LogError($"ImageScaler.GetReadyState() failed: {ex.Message}"); // If we can't get the state, treat it as disabled by user - // The caller should check if it's Ready or NotReady return AIFeatureReadyState.DisabledByUser; } } + /// + /// Ensure the AI model is ready (download if needed). + /// Must be called on UI thread. + /// public static async Task EnsureModelReadyAsync(IProgress progress = null) { + // Ensure we're on UI thread for Windows AI API calls + if (Application.Current?.Dispatcher != null && !Application.Current.Dispatcher.CheckAccess()) + { + return await Application.Current.Dispatcher.InvokeAsync(() => EnsureModelReadyAsync(progress)).Task.Unwrap(); + } + try { + Logger.LogInfo($"ImageScaler.EnsureReadyAsync() called on thread {Thread.CurrentThread.ManagedThreadId}"); var operation = ImageScaler.EnsureReadyAsync(); // Register progress handler if provided @@ -83,10 +118,13 @@ namespace ImageResizer.Services }; } - return await operation; + var result = await operation; + Logger.LogInfo($"ImageScaler.EnsureReadyAsync() completed: Status={result?.Status}, ExtendedError={result?.ExtendedError}"); + return result; } - catch (Exception) + catch (Exception ex) { + Logger.LogError($"ImageScaler.EnsureReadyAsync() failed: {ex.Message}"); return null; } } diff --git a/src/modules/imageresizer/ui/ViewModels/InputViewModel.cs b/src/modules/imageresizer/ui/ViewModels/InputViewModel.cs index c241728276..4ef4a20344 100644 --- a/src/modules/imageresizer/ui/ViewModels/InputViewModel.cs +++ b/src/modules/imageresizer/ui/ViewModels/InputViewModel.cs @@ -402,6 +402,7 @@ namespace ImageResizer.ViewModels }); // Call EnsureReadyAsync to download and prepare the AI model + // Note: WinAiSuperResolutionService handles dispatching to UI thread internally var result = await WinAiSuperResolutionService.EnsureModelReadyAsync(progress); if (result?.Status == Microsoft.Windows.AI.AIFeatureReadyResultState.Success)