You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 04:15:39