Ich habe WPF-Programm und ich versuche, EF Core mit SQLite dort zu verwenden, und ich fand seltsames Verhalten. Auch wenn ich asynchrone Methoden wie ToArrayAsync () oder SaveChangesAsync () aufruft, gibt es bereits abgeschlossene Aufgaben zurück. Es bedeutet also, dass die Operation tatsächlich synchron ausgeführt wurde.
Es scheint, dass in EF- oder SQLite-Verbindungen ein Flag vorhanden sein sollte, das die sync / async-Ausführung steuert, aber ich habe es nicht gefunden.
Ich habe diesen Code für Tests verwendet:
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;
}
Das liegt daran, dass SQLite-Implementierungen von ADO.NET-Klassen ( DbConnection
, DbCommand
) synchron sind. Übergeordnete Klassen stellen Async
Methoden Async
, die wirklich synchron sind, und es ist Aufgabe des Anbieters, eine bessere Implementierung bereitzustellen. Zum Beispiel, hier ist die Implementierung von 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;
}
Wie Sie sehen, gibt es nichts asynchrones whatsover, und die zurückgegebene Aufgabe ist immer abgeschlossen.
Dasselbe gilt für alle Standard- Async
Implementierungen in DbCommand
: Sie verwenden entweder TaskCompletionSource
oder direkt Task.FromResult
.
SQLiteCommand überschreibt dieses Verhalten nicht, und wenn dies der Fall ist, wird explizit in Kommentaren zu den Methoden angegeben, dass die asynchrone Ausführung nicht unterstützt wird. Hier ist zum Beispiel die Implementierung (Überschreibung) von 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));
}
Im Gegensatz dazu überschreiben SqlConnection
und SqlCommand
Klassen das standardmäßige (synchrone) Verhalten und stellen wirklich asynchrone Implementierungen von Methoden wie OpenAsync
oder ExecuteReaderAsync
, sodass Sie mit dem SQL Server-Provider nicht das von Ihnen beobachtete Verhalten haben sollten.
Das Verhalten, das Sie beobachten, wird erwartet und ist bei Verwendung von SQLite nicht fehlerhaft.
Da Sie dies in der WPF-Anwendung verwenden - das würde bedeuten, dass trotz der Verwendung von async \ await Sie den UI-Thread für die Dauer der gesamten Operation blockiert. Am besten ist es in diesem Fall, async-Versionen überhaupt nicht zu verwenden und das Ganze über Task.Run
oder ein ähnliches Konstrukt an den Hintergrund-Thread zu Task.Run
.