在 C# 中使用带有异步功能的 SQLite

问题描述 投票:0回答:4

我正在尝试了解

async
/
await
关键字和用法,我想我已经掌握了基础知识。但我的 SQLite 代码中有些东西无法正常工作。

我在我一直在从事的一个简单项目中使用 SQLite.core NuGet 包。我注意到我编写的异步代码的行为并不异步(如我预期),因此我创建了一个更简单的测试项目来测试我的理解。

在我的测试代码中,我打开到内存数据库的连接(我对基于文件的数据库也有同样的问题。内存在测试代码中更容易),并发出单个“创建表”命令,使用

ExecuteNonQueryAsync
。我不会立即
await
获取结果,而是在最终使用
await
关键字之前向控制台写入一些内容。

我希望控制台命令在

ExecuteNonQueryAsync
完成之前执行,所以在我的测试中我应该看到“1 2 3 4”。但我得到的是“1 3 2 4”

我使用 SQL Server LocalDB 连接运行相同的测试(运行相同的代码,只有

DbConnection
不同),并得到预期的“1 2 3 4”。所以我想我对
async
的基本理解并没有离目标太远。

我错过了什么?我是否需要在 SQLite 中使用特殊的连接字符串才能支持

async
方法?还支持吗?

我的完整测试项目可以在这里找到。

这是主程序本身:

 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
c# sqlite async-await
4个回答
16
投票

System.Data.SQLite
实施是 100% 同步的。它们没有任何异步重载,这要归咎于 Microsoft 造成的误解,因为
SQLiteCommand
使用仅调用同步版本的 *Async 方法的默认实现扩展了
System.Data.Common.DbCommand

/// <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());
  ...
}

我只是认为同样困难,我对他们采取的方法不满意,但这就是我们得到的。仅供记录,我认为应该有

NotSupportedException


3
投票

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

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


2
投票

几乎所有 ADO.NET 数据库提供程序实现都是同步的(异步 -> 同步实现的内部路径),除了 MS SQL Server 的实现,它是完全异步的!

System.Data.SQLite .NET 数据提供程序也是同步的,因为您可以使用 Write Ahead Logging (https://www.sqlite.org/wal.html),这里也建议您使用。始终使用事务块来最大程度地减少线程阻塞(极大地加快速度)!

AFAIK 同步 .NET 数据提供程序:

  • 所有 SQLite 提供商
  • Sybase (SQL Anywhere)
  • PostgreSQL
  • Oracle MySQL 数据提供者

异步有:

  • 位于 System.Data 中的 MS SQL Server 数据提供者
  • 用于 MySQL 的 MySqlConnector
  • DevArt 商业提供商,但他们没有 TAP(任务异步 图案)

也许编写自己的扩展并将方法放入 Task.Run(() => Func()) 中。

提示:当您想要检查“.NET 数据提供程序代码”是否在与主进程线程不同的线程中运行时,请在 Visual Studio 或 VS code 的调试菜单中显示线程窗口

问候


0
投票

这是一篇旧帖子,有很多观点,所以我想在这里澄清一些事情。 C# 中的任务永远不会保证其自身的并行性。例如,任务调度程序或同步上下文可能在同一线程中运行该工作。此外,方法调用将同步运行,直到在其中注册对 Task.Run 或某些 IO 完成处理程序的调用,这可能是调用堆栈中的许多级别。不要依赖它 - 如果您需要将代码置于后台,请使用

Task.Run
。一般来说,您不想在库代码中这样做,但您最了解自己的情况。

无论如何,Microsoft 文档表明他们的 SQLite 库是同步的(请参阅其文档的备注

© www.soinside.com 2019 - 2024. All rights reserved.