Ich schrieb vor kurzem den folgenden Code:async/erwarten vs. handgemachte Fortsetzungen: wird ExecuteSynchronously cleverly verwendet?
Task<T> ExecAsync<T>(string connectionString, SqlCommand cmd, Func<SqlCommand, T> resultBuilder, CancellationToken cancellationToken = default(CancellationToken))
{
var tcs = new TaskCompletionSource<T>();
SqlConnectionProvider p;
try
{
p = GetProvider(connectionString);
Task<IDisposable> openTask = p.AcquireConnectionAsync(cmd, cancellationToken);
openTask
.ContinueWith(open =>
{
if(open.IsFaulted) tcs.SetException(open.Exception.InnerExceptions);
else if(open.IsCanceled) tcs.SetCanceled();
else
{
var execTask = cmd.ExecuteNonQueryAsync(cancellationToken);
execTask.ContinueWith(exec =>
{
if(exec.IsFaulted) tcs.SetException(exec.Exception.InnerExceptions);
else if(exec.IsCanceled) tcs.SetCanceled();
else
{
try
{
tcs.SetResult(resultBuilder(cmd));
}
catch(Exception exc) { tcs.TrySetException(exc); }
}
}, TaskContinuationOptions.ExecuteSynchronously);
}
})
.ContinueWith(_ =>
{
if(!openTask.IsFaulted) openTask.Result.Dispose();
}, TaskContinuationOptions.ExecuteSynchronously);
}
catch(Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
Das wie vorgesehen funktioniert. Der gleiche Code mit Asynchron geschrieben/erwarten ist (natürlich) einfacher:
async Task<T> ExecAsync<T>(string connectionString, SqlCommand cmd, Func<SqlCommand, T> resultBuilder, CancellationToken cancellationToken = default(CancellationToken))
{
SqlConnectionProvider p = GetProvider(connectionString);
using(IDisposable openTask = await p.AcquireConnectionAsync(cmd, cancellationToken))
{
await cmd.ExecuteNonQueryAsync(cancellationToken);
return resultBuilder(cmd);
}
}
Ich hatte einen kurzen Blick auf die erzeugte IL für die 2 Versionen: die Asynchron/await ist größer (keine Überraschung), aber ich habe mich gefragt, wenn der async/erwarten Code-Generator die Tatsache analysiert, dass eine Fortsetzung tatsächlich synchron ist, um TaskContinuationOptions.ExecuteSynchronously
zu verwenden, wo es kann ... und ich fand es nicht in dem IL-generierten Code.
Wenn jemand weiß, diese oder haben keine Ahnung davon, würde ich gerne wissen!
Die Async-Version ist eigentlich nicht synchron. Woher hast du diese Idee? – Martijn
@Martijn: Sorry es war nicht klar. Mein Punkt ist, dass, wenn die Fortsetzung ist nur 'if (! OpenTask.IsFaulted) openTask.Result.Dispose();', Ich möchte es direkt auf der "vorherigen" laufenden Aufgabe ausgeführt werden, um jede Scheduling/Kontextwechsel zu vermeiden . – Spi
Vom Dekompilieren von TaskAwaiter Ich denke, das kann nicht mit dem eingebauten Erwarter getan werden. Sie müssen Ihren eigenen Warter schreiben. Wahrscheinlich nicht wert. – usr