C# 在C中使用带异步的SQLite#

C# 在C中使用带异步的SQLite#,c#,sqlite,async-await,C#,Sqlite,Async Await,我正在努力了解async/wait关键字和用法,我想我已经掌握了基本知识。但是在我的SQLite代码中有些地方不正常 我正在一个简单的项目中使用。我注意到我编写的异步代码并不是异步的(就像我预期的那样),所以我创建了一个更简单的测试项目来测试我的理解 在我的测试代码中,我打开了与内存中数据库的连接(基于文件的数据库也有同样的问题。在测试代码中,内存中更容易),并使用ExecuteNonQueryAsync发出一个“create table”命令。我不会立即wait等待结果,而是在最终使用wait

我正在努力了解
async
/
wait
关键字和用法,我想我已经掌握了基本知识。但是在我的SQLite代码中有些地方不正常

我正在一个简单的项目中使用。我注意到我编写的异步代码并不是异步的(就像我预期的那样),所以我创建了一个更简单的测试项目来测试我的理解

在我的测试代码中,我打开了与内存中数据库的连接(基于文件的数据库也有同样的问题。在测试代码中,内存中更容易),并使用
ExecuteNonQueryAsync
发出一个“create table”命令。我不会立即
wait
等待结果,而是在最终使用
wait
关键字之前,向控制台写入一些内容

我希望控制台命令在
ExecuteNonQueryAsync
完成之前执行,因此在测试中我应该看到“1 2 3 4”。但我得到的却是“1234”

我使用SQLServerLocalDB连接运行了相同的测试(运行相同的代码,只有
DbConnection
不同),得到了预期的“1234”。因此,我想我对
async
的基本理解离目标并不遥远

我错过了什么?为了支持
async
方法,我是否需要在SQLite中使用特殊的连接字符串?它甚至支持它吗

我的完整测试项目可以找到

这是主要的程序本身:

 namespace DatabaseTest
   {
    using System;
    using System.Data.Common;
    using System.Data.SqlClient;
    using System.Data.SQLite;
    using System.Threading.Tasks;
class Program
{
    static void Main(string[] args)
    {
        Task.WaitAll(TestDatabase(true), TestDatabase(false));
    }

    private static async Task TestDatabase(bool sqLite)
    {
        Console.WriteLine("Testing database, sqLite: {0}", sqLite);
        using (var connection = CreateConnection(sqLite))
        {
            connection.Open();
            var task = ExecuteNonQueryAsync(connection);
            Console.WriteLine("2");
            await task;
            Console.WriteLine("4");
        }
    }

    private static DbConnection CreateConnection(bool sqLite)
    {
        return sqLite ?
            (DbConnection)new SQLiteConnection(string.Format("Data Source=:memory:;")) :
            new SqlConnection(@"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\DatabaseTest.mdf;Integrated Security=True;Connect Timeout=30");
    }

    private static async Task ExecuteNonQueryAsync(DbConnection connection)
    {
        var command = connection.CreateCommand();
        command.CommandText = "CREATE TABLE test (col1 integer);";
        Console.WriteLine("1");
        await command.ExecuteNonQueryAsync();
        Console.WriteLine("3");
    }
}
以及输出:

Testing database, sqLite: True
1
3
2
4
Testing database, sqLite: False
1
2
3
4

启动异步任务后,该任务和主线程都可以继续运行。 所以不能保证哪一个跑得更快


SQLite是一个嵌入式数据库,没有客户机/服务器通信开销,并且作为一个库,在同一个CPU上运行。因此,这个实现可能已经决定,实际上支持异步执行是没有意义的。

System.Data.SQLite实现是100%同步的。它们没有任何异步重载,这是Microsoft造成这种误解的原因,因为
SQLiteCommand
扩展了
System.Data.Common.DbCommand
,默认实现了只调用同步版本的*async方法:

/// <summary>This is the asynchronous version of <see cref="M:System.Data.Common.DbCommand.ExecuteNonQuery" />. Providers should override with an appropriate implementation. The cancellation token may optionally be ignored.The default implementation invokes the synchronous <see cref="M:System.Data.Common.DbCommand.ExecuteNonQuery" /> method and returns a completed task, blocking the calling thread. The default implementation will return a cancelled task if passed an already cancelled cancellation token.  Exceptions thrown by <see cref="M:System.Data.Common.DbCommand.ExecuteNonQuery" /> will be communicated via the returned Task Exception property.Do not invoke other methods and properties of the <see langword="DbCommand" /> object until the returned Task is complete.</summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="T:System.Data.Common.DbException">An error occurred while executing the command text.</exception>
public virtual Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken)
{
  ...
  return Task.FromResult<int>(this.ExecuteNonQuery());
  ...
}
///这是的异步版本。提供程序应使用适当的实现重写。可以选择忽略取消令牌。默认实现调用同步方法并返回已完成的任务,从而阻塞调用线程。如果传递了已取消的取消令牌,则默认实现将返回已取消的任务。由引发的异常将通过返回的任务异常属性进行通信。在返回的任务完成之前,不要调用对象的其他方法和属性。
///用于监视取消请求的令牌。
///表示异步操作的任务。
///执行命令文本时出错。
公共虚拟任务ExecuteOnQueryAsync(CancellationToken CancellationToken)
{
...
返回Task.FromResult(this.ExecuteNonQuery());
...
}

我只是以同样艰难的方式思考,我对他们采取的方法不满意,但这就是我们得到的。为了记录在案,我认为应该有
NotSupportedException

你能给我更多的信息吗?我真的不知道为什么会发生这种情况。“作为一个库,在同一个CPU上运行”它如何保证在完全相同的CPU上运行?托管库没有这样的控制。@YuvalItzchakov不能保证。但从操作系统的角度来看,对库的函数调用并不是改变该进程调度的理由。(对于SQL Server,数据库代码总是在命名管道的另一端异步运行。)因此,基本上,我“不必担心它”,并使用
wait
编写代码。如果SQLite决定需要使用不同的线程,我将获得好处。如果它运行“足够快”,我就不在乎了。为此,我想知道为什么
ExecuteReaderAsync
返回了一个
DbDataReader
。与
Microsoft.Data.Sqlite
实现相同,异步ADO.NET方法将同步执行