J'ai un programme WPF et j'essaie d'utiliser EF Core avec SQLite et j'ai trouvé un comportement étrange. Même si j'appelle une méthode asynchrone telle que ToArrayAsync () ou SaveChangesAsync (), elle renvoie la tâche déjà terminée. Cela signifie donc que l'opération a été effectuée de manière synchrone.
Il semble qu'il y ait un indicateur dans la connexion EF ou SQLite qui contrôle l'exécution sync / async, mais je ne l'ai pas trouvé.
J'ai utilisé ce code pour les tests:
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;
}
En effet, les implémentations SQLite des classes ADO.NET ( DbConnection
, DbCommand
) sont synchrones. Les classes Async
fournissent des méthodes Async
vraiment synchrones et le travail du fournisseur consiste à fournir une meilleure implémentation. Par exemple, voici l'implémentation de 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;
}
Comme vous le voyez, rien n’est asynchrone et la tâche renvoyée est toujours terminée.
La même chose vaut pour tous par défaut Async
implémentations dans DbCommand
: tous utilisent soit TaskCompletionSource
ou directement Task.FromResult
.
SQLiteCommand ne redéfinit pas ce comportement et, le cas échéant, indique explicitement dans les commentaires aux méthodes que l'exécution asynchrone n'est pas prise en charge. Par exemple, voici l'implémentation ( ExecuteReaderAsync
) de 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));
}
En revanche, les SqlConnection
et SqlCommand
remplacent le comportement par défaut (synchornous) et fournissent des implémentations vraiment asynchrones de méthodes telles que OpenAsync
ou ExecuteReaderAsync
. Par conséquent, avec le fournisseur de serveur SQL, vous ne devriez pas avoir le comportement observé.
Donc, le comportement que vous observez est attendu et n’est pas gênant lors de l’utilisation de SQLite.
Puisque vous utilisez ceci dans l'application WPF, cela signifie que malgré l'utilisation asynchrone, vous attendez que le thread d'interface utilisateur soit bloqué pendant toute la durée de l'opération. La meilleure chose à faire dans ce cas n’est donc pas du tout d’utiliser des versions asynchrones et d’envoyer le tout au fil d’arrière-plan via Task.Run
ou une construction similaire.