异步函数阻止我的 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,直到加载数据并且我不明白为什么。

有人可以向我解释我的失败吗?我不明白为什么异步函数不会异步运行?

问候

异步函数阻止我的 WPF 应用程序

您确实需要使用调试器逐步执行,以确定哪个步骤需要很长时间。最有可能的候选者是 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 提供程序都用作其特定类的基础的 DbConnectionDbCommand 类中的缺陷。缺陷是默认情况下所有Async方法都是同步的!它甚至被记录在案!

例如,DbConnnection.OpenAsync的文档:

默认实现调用同步 Open 调用并返回已完成的任务。

对于 DbCommand.ExecuteDbDataReaderAsync:

默认实现调用同步 ExecuteReader 方法并返回已完成的任务,从而阻止调用线程

据我所知,只有SqlServer提供程序使用真正的异步实现覆盖异步方法。但是由于您正在使用OleDb提供商,因此您不走运。