异步函数阻止我的 WPF 应用程序
本文关键字:WPF 应用程序 我的 函数 异步 | 更新日期: 2023-09-27 18:34:23
我试着熟悉async/await。因此,我尝试编写一个 C#/WPF 程序来异步查询数据库,而不会阻止我的 GUI。
我创建了一个实现INotifyPropertyChanged
接口的对象。此对象提供了一个 DataTable
属性,这应该由我的异步函数更改。我的 GUI 组件具有与 DataTable
属性的绑定。
我的对象看起来像这样:
public class AsyncDataDemo : INotifyPropertyChanged
{
protected DataTable data = new DataTable();
public DataTable Data
{
get { return data; }
protected set
{
data = value;
doPropertyChanged("Data");
}
}
protected virtual void doPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs Arguments = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, Arguments);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected async Task<DataTable> OpenQueryAsync(string ConnectionString, string Query)
{
OdbcConnection connection = new OdbcConnection(ConnectionString);
await connection.OpenAsync().ConfigureAwait(false);
OdbcCommand command = new OdbcCommand(Query, connection);
DbDataReader dataReader = await command.ExecuteReaderAsync().ConfigureAwait(false);
DataTable resultData = new DataTable();
resultData.Load(dataReader);
connection.Close();
return resultData;
}
public async void RunQueryAsync(string Query)
{
Data = await OpenQueryAsync("<ConectionString>", (Query as string)).ConfigureAwait(false);
}
}
在按钮单击事件中,我调用:
private void Button_Click(object sender, RoutedEventArgs e)
{
data.RunQueryAsync("SELECT * FROM BigTable");
}
除了一个例外,这工作正常:按钮单击会阻止我的 GUI,直到加载数据并且我不明白为什么。
有人可以向我解释我的失败吗?我不明白为什么异步函数不会异步运行?
问候
您确实需要使用调试器逐步执行,以确定哪个步骤需要很长时间。最有可能的候选者是 resultData.Load(dataReader);
,您的代码仍然可以在 UI 线程上的方式是,如果您调用的每个函数都返回一个已经具有 .IsCompleted == true
的任务。
如果任务已完成,并且您await
即使您执行了ConfigureAwait(false)
,仍保留在 UI 线程上。所有需要发生的是OpenAsync()
,ExecuteReaderAsync()
需要非常快地完成或同步完成(两者都很有可能)。
解决此问题的一种方法是将查询放在后台线程上启动,而不是等待ConfigureAwait(false)
为您执行此操作。
public async void RunQueryAsync(string Query)
{
Data = await Task.Run(() => OpenQueryAsync("<ConectionString>", (Query as string)));
}
我还删除了ConfigureAwait(false)
,因为您希望INotifyPropertyChanged
发生在 UI 线程上。
此外,您真的需要处理一次性物品。
protected async Task<DataTable> OpenQueryAsync(string ConnectionString, string Query)
{
using(OdbcConnection connection = new OdbcConnection(ConnectionString))
{
await connection.OpenAsync().ConfigureAwait(false);
using(OdbcCommand command = new OdbcCommand(Query, connection))
using(DbDataReader dataReader = await command.ExecuteReaderAsync().ConfigureAwait(false))
{
DataTable resultData = new DataTable();
resultData.Load(dataReader);
return resultData;
}
}
}
遇到的行为的原因是所有 ADO 提供程序都用作其特定类的基础的 DbConnection
和 DbCommand
类中的缺陷。缺陷是默认情况下所有Async
方法都是同步的!它甚至被记录在案!
例如,DbConnnection.OpenAsync的文档:
默认实现调用同步 Open 调用并返回已完成的任务。
对于 DbCommand.ExecuteDbDataReaderAsync:
默认实现调用同步 ExecuteReader 方法并返回已完成的任务,从而阻止调用线程。
据我所知,只有SqlServer
提供程序使用真正的异步实现覆盖异步方法。但是由于您正在使用OleDb
提供商,因此您不走运。