WinUI 3(.NET 7)SQL异步操作报错:值不在预期范围内
问题分析与解决方案
首先明确:你最初遇到的**"Value does not fall within the expected range"错误并非数据库异步操作导致**,调用栈明确指向ItemsControl.set_ItemsSource,本质是UI线程绑定的线程安全问题;后续出现的await挂起则是异步操作上下文阻塞导致的死锁。以下是针对性解决方案:
1. 修复UI绑定的线程安全问题
WinUI的UI元素必须在Dispatcher线程上更新,直接在构造函数中同步等待异步方法并赋值ItemsSource会触发线程错误。
错误示例(MainPage构造函数)
// 错误:同步等待异步任务导致线程上下文冲突 public MainPage() { InitializeComponent(); var vm = new AsyncViewModel(); var result = vm.GetAllStarsAsync().Result; // 阻塞UI线程,引发后续绑定错误 InventoryList.ItemsSource = result; }
正确异步初始化方式
改用Loaded事件触发异步操作,并确保在Dispatcher线程上更新UI:
public MainPage() { InitializeComponent(); Loaded += MainPage_Loaded; } private async void MainPage_Loaded(object sender, RoutedEventArgs e) { var vm = new AsyncViewModel(); var stars = await vm.GetAllStarsAsync(); // 显式切换到UI线程赋值,避免跨线程访问UI元素 await DispatcherQueue.EnqueueAsync(() => { InventoryList.ItemsSource = stars; }); }
2. 优化ViewModel的异步集合操作
ObservableCollection的修改必须在UI线程执行,建议在ViewModel中声明可绑定的集合属性,避免手动赋值ItemsSource:
using Microsoft.Data.SqlClient; // 替换旧版System.Data.SqlClient using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.UI.Dispatching; public partial class AsyncViewModel : ObservableRecipient { [ObservableProperty] private ObservableCollection<Star> _stars = new(); private readonly DispatcherQueue _dispatcherQueue; public AsyncViewModel(DispatcherQueue dispatcherQueue) { _dispatcherQueue = dispatcherQueue; } public async Task LoadStarsAsync() { var cnStr = @"Data Source=.\SQLExpress;Initial Catalog=Company;Integrated Security=True;MultipleActiveResultSets=True"; const string query = "select * from Star"; using var cn = new SqlConnection(cnStr); await cn.OpenAsync(); using var cmd = new SqlCommand(query, cn); using var reader = await cmd.ExecuteReaderAsync(); // 清空现有数据(必须在UI线程) await _dispatcherQueue.EnqueueAsync(() => Stars.Clear()); while (reader.Read()) { var star = new Star { Id = (int)reader["Id"], Name = (string)reader["Name"] }; // 新增元素到ObservableCollection(必须在UI线程) await _dispatcherQueue.EnqueueAsync(() => Stars.Add(star)); } } }
对应XAML绑定
<ListView x:Name="InventoryList" ItemsSource="{x:Bind ViewModel.Stars, Mode=OneWay}" />
MainPage调用
private readonly AsyncViewModel _viewModel; public MainPage() { InitializeComponent(); _viewModel = new AsyncViewModel(DispatcherQueue.GetForCurrentThread()); Loaded += async (s, e) => await _viewModel.LoadStarsAsync(); }
3. 解决SqlClient异步死锁问题
若遇到await挂起(死锁),核心原因是:WinUI的SynchronizationContext会捕获UI线程上下文,同步等待(.Result/.Wait())会导致上下文阻塞。需遵守以下规则:
- 绝对禁止在UI线程使用
.Result或.Wait()等待异步任务 - 所有数据库异步操作全程使用
await,不阻塞执行流 - 若需更新UI,必须通过
DispatcherQueue切换到UI线程
4. 替换为Microsoft.Data.SqlClient
System.Data.SqlClient是旧版库,对WinUI和.NET 7的兼容性较差,建议替换为官方推荐的Microsoft.Data.SqlClient:
- 卸载项目中的
System.Data.SqlClient引用 - 通过NuGet安装
Microsoft.Data.SqlClient包 - 修改代码中的命名空间为
using Microsoft.Data.SqlClient;
内容的提问来源于stack exchange,提问作者WCS
相关产品推荐
相关产品推荐

