异步功能阻止我的wpf应用程序

时间:2016-02-11 16:49:19

标签: c# wpf async-await

我尝试熟悉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直到加载数据并且我不明白为什么。

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

此致

2 个答案:

答案 0 :(得分:5)

你真的需要通过一个调试器来确定哪个步骤需要很长时间。最可能的候选者是resultData.Load(dataReader);,如果您调用的每个函数都返回已经有.IsCompleted == true的任务,那么代码仍然可以在UI线程上的方式。

如果任务已经完成,并且您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;
        }
    }
}

答案 1 :(得分:5)

您遇到的行为的原因是DbConnectionDbCommand类中的一个缺陷,所有ADO提供程序都将这些缺陷用作其特定类的基础。缺点是默认情况下所有Async方法都是同步!它甚至记录在案!

例如,DbConnnection.OpenAsync的文档:

  

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

DbCommand.ExecuteDbDataReaderAsync

  

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

从我所看到的,只有SqlServer提供程序使用真正的异步实现覆盖异步方法。但由于您使用的是OleDb提供商,因此您运气不佳。