Tengo un programa WPF y estoy tratando de usar EF Core con SQLite allí y encontré un comportamiento extraño. Incluso si llamo a un método asíncrono como ToArrayAsync () o SaveChangesAsync (), devuelve la tarea ya completada. Por lo tanto, significa que la operación se realizó de forma síncrona.
Parece que debería haber algún indicador en la conexión EF o SQLite que controle la ejecución de sincronización / asíncrono pero no lo encontré.
Utilicé este código para las pruebas:
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;
}
Esto se debe a que las implementaciones de SQLite de las clases ADO.NET ( DbConnection
, DbCommand
) son síncronas. Las clases para padres proporcionan métodos Async
que son realmente síncronos, y es un trabajo del proveedor proporcionar una mejor implementación. Por ejemplo, aquí está la implementación 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;
}
Como ve, no hay nada asíncrono en absoluto, y la tarea devuelta siempre se completa.
Lo mismo ocurre con todas las implementaciones Async
predeterminadas en DbCommand
: todas usan TaskCompletionSource
o directamente Task.FromResult
.
SQLiteCommand no anula ese comportamiento, y cuando lo hace, dice explícitamente en los comentarios a los métodos que la ejecución asíncrona no es compatible. Por ejemplo, aquí está la implementación (anulación) 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));
}
Por el contrario, las clases SqlConnection
y SqlCommand
anulan el comportamiento predeterminado (sincrónico) y proporcionan implementaciones realmente asíncronas de métodos como OpenAsync
o ExecuteReaderAsync
, por lo que con el proveedor de SQL Server no debería tener el comportamiento que observa.
Por lo tanto, el comportamiento que observa es esperado y no presenta errores cuando se usa SQLite.
Ya que está usando esto en la aplicación WPF, eso significaría que, a pesar de usar async \ le espera, el subproceso de la interfaz de usuario se bloqueará durante toda la operación. Por lo tanto, lo mejor que se puede hacer en este caso es no usar las versiones asíncronas en absoluto y Task.Run
por Task.Run
subproceso en Task.Run
plano a través de Task.Run
o una construcción similar.