实体框架Core + SQlite.异步请求实际上是同步的

Iva*_*kov 6 .net c# sqlite asynchronous entity-framework-core

我有WPF程序,我试图在那里使用EF Core和SQLite,我发现了奇怪的行为.即使我调用ToArrayAsync()或SaveChangesAsync()之类的异步方法,它也会返回已完成的任务.所以这意味着操作实际上是同步完成的.

似乎EF或SQLite连接中应该有一些控制同步/异步执行的标志,但我没有找到它.

我用这个代码进行测试:

using (var context = new TestDbContext())
{
    //I have about 10000 records here.
    var task = context.Users.ToListAsync();
    if (task.IsCompleted && task.Result != null)
    {
        // It is always comes here.
    }
    await task;
}
Run Code Online (Sandbox Code Playgroud)

Evk*_*Evk 5

这是因为 ADO.NET 类 ( DbConnection, DbCommand) 的SQLite 实现是同步的。父类提供Async真正同步的方法,提供更好的实现是提供者的工作。例如,这里是实现DbConnection.OpenAsync

public virtual Task OpenAsync(CancellationToken cancellationToken)
{
  TaskCompletionSource<object> completionSource = new TaskCompletionSource<object>();
  if (cancellationToken.IsCancellationRequested)
  {
    completionSource.SetCanceled();
  }
  else
  {
    try
    {
      this.Open();
      completionSource.SetResult((object) null);
    }
    catch (Exception ex)
    {
      completionSource.SetException(ex);
    }
  }
  return (Task) completionSource.Task;
}
Run Code Online (Sandbox Code Playgroud)

如您所见,没有任何异步内容,返回的任务始终已完成。

中的所有默认Async实现也是如此DbCommand:它们都使用TaskCompletionSource或直接使用Task.FromResult

SQLiteCommand 不会覆盖该行为,并且当它覆盖时 - 它在对不支持异步执行的方法的注释中明确指出。例如,这里是实现(覆盖)ExecuteReaderAsync

/// <summary>
/// Executes the <see cref="P:Microsoft.Data.Sqlite.SqliteCommand.CommandText" /> asynchronously against the database and returns a data reader.
/// </summary>
/// <param name="behavior">A description of query's results and its effect on the database.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <remarks>
/// SQLite does not support asynchronous execution. Use write-ahead logging instead.
/// </remarks>
/// <seealso href="http://sqlite.org/wal.html">Write-Ahead Logging</seealso>
public virtual Task<SqliteDataReader> ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
{
  cancellationToken.ThrowIfCancellationRequested();
  return Task.FromResult<SqliteDataReader>(this.ExecuteReader(behavior));
}
Run Code Online (Sandbox Code Playgroud)

相比之下SqlConnectionSqlCommand类确实会覆盖默认(同步)行为并提供真正异步的方法实现,例如OpenAsyncor ExecuteReaderAsync,因此对于 sql server provider,您不应该有您观察到的行为。

因此,在使用 SQLite 时,您观察到的行为是预期的,而不是错误的。

由于您在 WPF 应用程序中使用它 - 这意味着尽管使用 async\await 您的 UI 线程将在整个操作期间被阻塞。因此,在这种情况下最好的做法是根本不使用异步版本,而是通过Task.Run或类似的构造将整个内容分派到后台线程。