async/await与手工制作的延续:是巧妙地使用ExecuteSynchronously吗?

Spi*_*Spi 6 c# continuations asynchronous async-await

我最近写了以下代码:

    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;
    }
Run Code Online (Sandbox Code Playgroud)

这按预期工作.用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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

我快速浏览了两个版本生成的IL:async/await更大(不是一个惊喜)但我想知道async/await代码生成器是否分析了一个事实,即continuation实际上是同步的TaskContinuationOptions.ExecuteSynchronously,它可以在哪里使用...而我在IL生成的代码中找不到这个.

如果有人知道这个或有任何线索,我很高兴知道!

nos*_*tio 10

我想知道async/await代码生成器是否分析了一个事实,即continuation实际上是同步使用 TaskContinuationOptions.ExecuteSynchronously它可以...而我在IL生成的代码中找不到这个.

是否await延续 - 没有ConfigureAwait(continueOnCapturedContext: false) - 异步或同步执行取决于执行代码的线程上是否存在同步上下文await.如果SynchronizationContext.Current != null,进一步的行为取决于实施SynchronizationContext.Post.

例如,如果您在WPF/WinForms应用程序的主UI线程上,您的延续将在同一个线程上执行,但仍然是异步的,在消息循环的某个未来迭代中.它将通过发布SynchronizationContext.Post.这是因为先前的任务已经在线程池线程上完成,或者在不同的同步上下文中完成(例如,为什么每个Dispatcher.BeginInvoke回调的唯一同步上下文?).

如果先前任务已在具有相同同步上下文的线程上完成(例如,WinForm UI线程),await则将同步执行延续(内联).SynchronizationContext.Post在这种情况下不会使用.

在没有同步上下文的情况下,await将在先前任务完成的同一线程上同步执行继续.

ContinueWith与它的TaskContinuationOptions.ExecuteSynchronously实现有什么不同,实现完全不关心初始线程或完成线程的同步上下文,并且总是同步执行延续(尽管如此,这种行为也有例外).

您可以使用ConfigureAwait(continueOnCapturedContext: false)更接近所需的行为,但它的语义仍然不同TaskContinuationOptions.ExecuteSynchronously.实际上,它指示调度程序不在具有任何同步上下文的线程上运行延续,因此您可能会遇到ConfigureAwait(false) 将连续推送到线程池的情况,而您可能期望同步执行.

还有关系:重访Task.ConfigureAwait(continueOnCapturedContext: false).