實體框架Core + SQlite。異步請求實際上是同步的

.net asynchronous c# entity-framework-core sqlite

我有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;
}

一般承認的答案

那是因為ADO.NET類( DbConnectionDbCommand )的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;
}

如您所見,沒有任何異步的東西,並且返回的任務總是完成。

對於DbCommand所有默認Async實現也是如此:它們都使用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));
}

相比之下 - SqlConnectionSqlCommand類確實覆蓋默認(同步)行為並提供OpenAsyncExecuteReaderAsync等方法的真正異步實現,因此對於sql server provider,您不應該有您觀察到的行為。

因此,在使用SQLite時,您觀察到的行為是預期的而不是錯誤的。

由於您在WPF應用程序中使用它 - 這意味著儘管使用async \ await,您的UI線程將在整個操作期間被阻止。因此,在這種情況下最好的做法是不要使用異步版本,並通過Task.Run或類似的構造將整個事件分發給後台線程。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow