async/await vs. 手工制作的延续:ExecuteSynchronously 使用巧妙吗?
async/await vs. hand made continuations: is ExecuteSynchronously cleverly used?
我最近写了下面的代码:
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;
}
这按预期工作。使用 async/await 编写的相同代码(显然)更简单:
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 );
}
}
我快速查看了 2 个版本生成的 IL:async/await 更大(不足为奇)但我想知道 async/await 代码生成器是否分析了一个事实continuation 实际上是同步的,可以在它可以使用的地方使用 TaskContinuationOptions.ExecuteSynchronously
,而我在 IL 生成的代码中找不到它。
如果有人知道或对此有任何线索,我将很高兴知道!
此类优化是在任务调度程序级别完成的。任务调度程序不仅有一大堆任务要做;它将它们分成不同的任务,用于它拥有的每个工作线程。当从这些工作线程之一安排工作时(当你有很多延续时,这种情况经常发生)它会将它添加到该线程的队列中。这确保当您有一个带有一系列延续的操作时,线程之间的上下文切换被最小化。现在,如果一个线程用完了工作,它也可以从另一个线程的队列中提取工作项,这样每个人都可以保持忙碌。
当然,综上所述,您在代码中等待的实际任务中 none 实际上是 CPU 绑定工作;它们是 IO 绑定工作,因此它们不在工作线程上 运行 可以继续重新用于处理延续,因为 它们的工作不是由分配的线程第一.
I was wondering if the async/await code generator analyses the fact
that a continuation is actually synchronous to use
TaskContinuationOptions.ExecuteSynchronously
where it can... and I
failed to find this in the IL generated code.
await
continuations - without ConfigureAwait(continueOnCapturedContext: false
) - 异步或同步执行取决于执行你的线程上是否存在同步上下文当它到达 await
点时的代码。如果 SynchronizationContext.Current != null
,进一步的行为取决于 SynchronizationContext.Post
.
的实现
例如,如果您在 WPF/WinForms 应用程序的主 UI 线程上,您的延续将在同一线程上执行,但仍然 异步 ,在消息循环的某些未来迭代中。它将通过 SynchronizationContext.Post
发布。前提是先行任务已在线程池线程或不同的同步上下文(例如 Why a unique synchronization context for each Dispatcher.BeginInvoke callback?)上完成。
如果前面的任务已在具有相同同步上下文的线程(例如 WinForm UI 线程)上完成,await
延续将同步执行(内联)。 SynchronizationContext.Post
不会在这种情况下使用。
在没有同步上下文的情况下,await
延续将在先前任务已完成的同一线程上同步执行。
这就是它与 ContinueWith
和 TaskContinuationOptions.ExecuteSynchronously
实现的不同之处,它根本不关心初始线程或完成线程的同步上下文,并且始终执行延续同步(尽管如此,仍有 exceptions to this behavior)。
您可以使用 ConfigureAwait(continueOnCapturedContext: false)
来更接近所需的行为,但它的语义仍然不同于 TaskContinuationOptions.ExecuteSynchronously
。事实上,它指示调度程序 not 运行 在具有任何同步上下文的线程上继续,因此您可能会遇到 ConfigureAwait(false)
pushes the continuation to thread pool ,而您可能一直期待同步执行。
还相关:
我最近写了下面的代码:
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;
}
这按预期工作。使用 async/await 编写的相同代码(显然)更简单:
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 );
}
}
我快速查看了 2 个版本生成的 IL:async/await 更大(不足为奇)但我想知道 async/await 代码生成器是否分析了一个事实continuation 实际上是同步的,可以在它可以使用的地方使用 TaskContinuationOptions.ExecuteSynchronously
,而我在 IL 生成的代码中找不到它。
如果有人知道或对此有任何线索,我将很高兴知道!
此类优化是在任务调度程序级别完成的。任务调度程序不仅有一大堆任务要做;它将它们分成不同的任务,用于它拥有的每个工作线程。当从这些工作线程之一安排工作时(当你有很多延续时,这种情况经常发生)它会将它添加到该线程的队列中。这确保当您有一个带有一系列延续的操作时,线程之间的上下文切换被最小化。现在,如果一个线程用完了工作,它也可以从另一个线程的队列中提取工作项,这样每个人都可以保持忙碌。
当然,综上所述,您在代码中等待的实际任务中 none 实际上是 CPU 绑定工作;它们是 IO 绑定工作,因此它们不在工作线程上 运行 可以继续重新用于处理延续,因为 它们的工作不是由分配的线程第一.
I was wondering if the async/await code generator analyses the fact that a continuation is actually synchronous to use
TaskContinuationOptions.ExecuteSynchronously
where it can... and I failed to find this in the IL generated code.
await
continuations - without ConfigureAwait(continueOnCapturedContext: false
) - 异步或同步执行取决于执行你的线程上是否存在同步上下文当它到达 await
点时的代码。如果 SynchronizationContext.Current != null
,进一步的行为取决于 SynchronizationContext.Post
.
例如,如果您在 WPF/WinForms 应用程序的主 UI 线程上,您的延续将在同一线程上执行,但仍然 异步 ,在消息循环的某些未来迭代中。它将通过 SynchronizationContext.Post
发布。前提是先行任务已在线程池线程或不同的同步上下文(例如 Why a unique synchronization context for each Dispatcher.BeginInvoke callback?)上完成。
如果前面的任务已在具有相同同步上下文的线程(例如 WinForm UI 线程)上完成,await
延续将同步执行(内联)。 SynchronizationContext.Post
不会在这种情况下使用。
在没有同步上下文的情况下,await
延续将在先前任务已完成的同一线程上同步执行。
这就是它与 ContinueWith
和 TaskContinuationOptions.ExecuteSynchronously
实现的不同之处,它根本不关心初始线程或完成线程的同步上下文,并且始终执行延续同步(尽管如此,仍有 exceptions to this behavior)。
您可以使用 ConfigureAwait(continueOnCapturedContext: false)
来更接近所需的行为,但它的语义仍然不同于 TaskContinuationOptions.ExecuteSynchronously
。事实上,它指示调度程序 not 运行 在具有任何同步上下文的线程上继续,因此您可能会遇到 ConfigureAwait(false)
pushes the continuation to thread pool ,而您可能一直期待同步执行。
还相关: