WinUI3多窗口应用:ServiceLocator.Current需基于线程获取作用域服务
解决方案:基于异步线程上下文绑定ServiceLocator作用域
你的问题核心在于当前ServiceLocator依赖激活窗口ID定位作用域,导致窗口A的异步后续操作因窗口B激活而拿到错误的作用域。要解决这个问题,我们可以用.NET的AsyncLocal<T>实现线程/异步上下文绑定的作用域定位,确保异步流中始终沿用发起线程的ServiceLocator实例。
步骤1:重构ServiceLocator类,引入AsyncLocal存储上下文
修改ServiceLocator核心逻辑,用AsyncLocal<ServiceLocator>替代原窗口ID直接获取逻辑,同时保留窗口ID到Locator的映射用于初始化和窗口切换绑定:
public class ServiceLocator { // 存储每个窗口对应的ServiceLocator实例 private static readonly Dictionary<int, ServiceLocator> _serviceLocators = new Dictionary<int, ServiceLocator>(); // 绑定当前线程/异步上下文的Locator实例 private static readonly AsyncLocal<ServiceLocator> _currentAsyncLocator = new AsyncLocal<ServiceLocator>(); static public ServiceLocator Current { get { var current = _currentAsyncLocator.Value; if (current == null) { // 初始化 fallback:如果当前上下文无绑定,用激活窗口ID获取 int viewId = WindowHelper.CurrentWindowId; current = _serviceLocators.GetOrAdd(viewId, id => new ServiceLocator()); _currentAsyncLocator.Value = current; } return current; } private set { _currentAsyncLocator.Value = value; } } // 绑定指定窗口ID的Locator到当前上下文 public static void BindToWindow(int windowId) { if (_serviceLocators.TryGetValue(windowId, out var locator)) { _currentAsyncLocator.Value = locator; } else { var newLocator = new ServiceLocator(); _serviceLocators[windowId] = newLocator; _currentAsyncLocator.Value = newLocator; } } // 窗口关闭时清理资源 public static void UnbindWindow(int windowId) { if (_serviceLocators.TryGetValue(windowId, out var locator)) { if (_currentAsyncLocator.Value?.Equals(locator) ?? false) { _currentAsyncLocator.Value = null; } _serviceLocators.Remove(windowId); } } // 自定义服务获取方法示例 public T GetService<T>() { // 你的DI服务获取逻辑,比如从当前作用域容器中获取 // ... } }
步骤2:在窗口初始化/激活时绑定上下文
每个窗口创建时,将自身对应的ServiceLocator绑定到当前线程上下文,确保窗口激活时上下文正确切换:
public sealed partial class AppWindow : Window { private readonly int _windowId; public AppWindow() { InitializeComponent(); _windowId = WindowHelper.GetWindowId(this); // 实现根据窗口实例获取ID的方法 // 初始化时绑定当前窗口的Locator到上下文 ServiceLocator.BindToWindow(_windowId); // 窗口激活时重新绑定,避免切换窗口时上下文混乱 Activated += (s, e) => { ServiceLocator.BindToWindow(_windowId); }; // 窗口关闭时清理资源 Closed += (s, e) => { ServiceLocator.UnbindWindow(_windowId); }; } }
步骤3:验证异步场景的正确性
当窗口A点击“保存并打印”按钮时,异步流程会保留AsyncLocal上下文:
private async void SaveAndPrintButton_Click(object sender, RoutedEventArgs e) { // 此时Current绑定的是窗口A的Locator var saveService = ServiceLocator.Current.GetService<ISaveService>(); await saveService.SaveDataAsync(); // 异步保存 // 打开窗口B,激活窗口切换为B,但当前异步上下文仍属于窗口A var printWindow = new PrintPreviewWindow(); printWindow.Activate(); // 后续操作仍能获取窗口A的作用域服务 var postSaveService = ServiceLocator.Current.GetService<IPostSaveService>(); postSaveService.DoPostSaveWork(); // 正确使用窗口A的作用域 }
关键原理说明
AsyncLocal<T>会跟随异步调用上下文传递,即使在异步操作中切换了UI线程,原来上下文里的AsyncLocal值不会被新窗口的激活操作覆盖。这样窗口A的异步后续操作始终能拿到自己的ServiceLocator实例。
内容的提问来源于stack exchange,提问作者NAVEEN V
相关产品推荐
相关产品推荐

