Ho un programma WPF e sto cercando di usare l'EF Core con SQLite e ho trovato un comportamento strano. Anche se chiamo il metodo async come ToArrayAsync () o SaveChangesAsync () restituisce l'attività già completata. Quindi significa che l'operazione è stata effettivamente eseguita in modo sincrono.
Sembra che ci dovrebbe essere qualche flag in EF o connessione SQLite che controlla l'esecuzione sincrona / asincrona ma non l'ho trovato.
Ho usato questo codice per i test:
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;
}
Questo perché le implementazioni SQLite delle classi ADO.NET ( DbConnection
, DbCommand
) sono sincrone. Le classi principali forniscono metodi Async
realmente sincroni, ed è compito del fornitore fornire un'implementazione migliore. Ad esempio, ecco l'implementazione di 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;
}
Come vedi, non c'è nulla di asincrono e l'attività restituita è sempre completata.
Lo stesso vale per tutte le implementazioni Async
predefinite in DbCommand
: tutte utilizzano TaskCompletionSource
o direttamente Task.FromResult
.
SQLiteCommand non sovrascrive quel comportamento e, quando lo fa, dice esplicitamente nei commenti ai metodi che l'esecuzione asincrona non è supportata. Ad esempio, ecco l'implementazione (overriden) di 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));
}
Al contrario, le classi SqlConnection
e SqlCommand
sovrascrivono il comportamento predefinito (sincrono) e forniscono implementazioni realmente asincrone di metodi come OpenAsync
o ExecuteReaderAsync
, quindi con il provider del server SQL non si dovrebbe avere il comportamento osservato.
Quindi il comportamento che si osserva è previsto e non è difettoso quando si utilizza SQLite.
Dal momento che stai usando questo in un'applicazione WPF, ciò significherebbe che nonostante l'uso asincrono \ ti aspetti che il thread dell'interfaccia utente venga bloccato per tutta la durata dell'intera oprazione. Quindi la cosa migliore da fare in questo caso non è usare le versioni asincrone e inviare tutto al thread in background tramite Task.Run
o un costrutto simile.