异步/伺机与手工制作的延续:是ExecuteSynchronously巧妙地利用? [英] async/await vs. hand made continuations: is ExecuteSynchronously cleverly used?

查看:232
本文介绍了异步/伺机与手工制作的延续:是ExecuteSynchronously巧妙地利用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近,我写了下面的code:

 任务< T> ExecAsync< T>(字符串的connectionString,CMD的SqlCommand,Func键<的SqlCommand,T> resultBuilder,的CancellationToken的CancellationToken =默认(的CancellationToken))
    {
        VAR TCS =新TaskCompletionSource< T>();        SqlConnectionProvider磷;
        尝试
        {
            P = GetProvider(的connectionString);
            任务<&的IDisposable GT; openTask = p.AcquireConnectionAsync(CMD,的CancellationToken);
            openTask
                .ContinueWith(开放=>
                {
                    如果(open.IsFaulted)tcs.SetException(open.Exception.InnerExceptions);
                    否则如果(open.IsCanceled)tcs.SetCanceled();
                    其他
                    {
                        VAR execTask = cmd.ExecuteNonQueryAsync(的CancellationToken);
                        execTask.ContinueWith(EXEC =>
                        {
                            如果(exec.IsFaulted)tcs.SetException(exec.Exception.InnerExceptions);
                            否则如果(exec.IsCanceled)tcs.SetCanceled();
                            其他
                            {
                                尝试
                                {
                                    tcs.SetResult(resultBuilder(CMD));
                                }
                                赶上(例外EXC){tcs.TrySetException(EXC); }
                            }
                        },TaskContinuationOptions.ExecuteSynchronously);
                    }
                })
                .ContinueWith(_ =>
                {
                    如果openTask.Result.Dispose()(openTask.IsFaulted!);
                },TaskContinuationOptions.ExecuteSynchronously);
        }
        赶上(异常前)
        {
            tcs.SetException(除息);
        }
        返回tcs.Task;
    }

这按预期工作。与异步写入相同code /等待是(显然)简单的:

 异步任务< T> ExecAsync< T>(字符串的connectionString,CMD的SqlCommand,Func键<的SqlCommand,T> resultBuilder,的CancellationToken的CancellationToken =默认(的CancellationToken))
{
    SqlConnectionProvider P = GetProvider(的connectionString);
    使用(IDisposable的openTask =等待p.AcquireConnectionAsync(CMD,的CancellationToken))
    {
        等待cmd.ExecuteNonQueryAsync(的CancellationToken);
        返回resultBuilder(CMD);
    }
}

我有一个快速浏览一下生成的IL的2个版本:异步/的await较大(不是一个惊喜),但我想知道如果异步/的await code发电机分析事实的延续其实是同步在那里可以......我没能找到这个在生成的IL code使用 TaskContinuationOptions.ExecuteSynchronously

如果有人知道这个或有任何关于它的线索,我会很高兴知道!


解决方案

  

我在想,如果异步/的await code发电机分析事实
  这延续实际上是同步使用
   TaskContinuationOptions.ExecuteSynchronously 在那里可以......我
  没能找到这个在生成的IL code。


无论伺机延续 - 的没有 ConfigureAwait(continueOnCapturedContext:假的 - 异步执行或同步取决于其执行你的code中的线程上同步上下文的presence当它击中了的await 点。如果 SynchronizationContext.Current!= NULL ,进一步的行为取决于实施 SynchronizationContext.Post

例如,如果你是一个WPF / WinForms应用程序的主UI线程上,你的延续将是相同的线程上执行,但仍异步的,在将来的某个消息循环的迭代。它将通过 SynchronizationContext.Post 公布。这是所提供的先行任务已完成了对一个线程池线程,或者在不同的同步环境(例如,<一href=\"http://stackoverflow.com/questions/22217477/why-a-unique-synchronization-context-for-each-dispatcher-begininvoke-callback\">Why每个Dispatcher.BeginInvoke回调独特的同步环境?)。

如果该先行任务已完成与同一同步上下文一个线程(例如,一个WinForm UI线程),在的await 延续将被执行的同步(内联)。 SynchronizationContext.Post 不会在这种情况下使用。

在没有同步上下文,一个的await 延续将被执行的同步同一个线程的先行任务已完成对

这是它是如何从你的 ContinueWith TaskContinuationOptions.ExecuteSynchronously 执行,这根本不关心不同所有关于要么初始线程或完成线程,始终同步情况下同步执行的延续(还有的例外行为尽管如此)。

您可以使用 ConfigureAwait(continueOnCapturedContext:FALSE)来接近所需的行为,但它的语义仍不同于 TaskContinuationOptions.ExecuteSynchronously 。事实上,它指示调度为不可以与任何同步上下文一个线程中运行的延续,所以你可能会遇到的情况,其中 ConfigureAwait(假) <一href=\"http://stackoverflow.com/questions/22672984/configureawait-pushes-the-continuation-to-a-pool-thread\">pushes延续到线程池,虽然你可能一直期待一个同步执行。

还有相关的:<一href=\"http://stackoverflow.com/questions/28410046/revisiting-task-configureawaitcontinueoncapturedcontext-false?lq=1\">Revisiting Task.ConfigureAwait(continueOnCapturedContext:假的)。

I recently wrote the following code:

    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;
    }

This works as intended. The same code written with async/await is (obviously) simpler:

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 );
    }
}

I had a quick look at the generated IL for the 2 versions: the async/await is bigger (not a surprise) but 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.

If anyone knows this or have any clue about it, I'd be pleased to know!

解决方案

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.

Whether await continuations - without ConfigureAwait(continueOnCapturedContext: false) - execute asynchronously or synchronously depends on the presence of a synchronization context on the thread which was executing your code when it hit the await point. If SynchronizationContext.Current != null, the further behavior depends on the implementation of SynchronizationContext.Post.

E.g., if you are on the main UI thread of a WPF/WinForms app, your continuations will be executed on the same thread, but still asynchronously, upon some future iteration of the message loop. It will be posted via SynchronizationContext.Post. That's provided the antecedent task has completed on a thread pool thread, or on a different synchronization context (e.g., Why a unique synchronization context for each Dispatcher.BeginInvoke callback?).

If the antecedent task has completed on a thread with the same synchronization context (e.g. a WinForm UI thread), the await continuation will be executed synchronously (inlined). SynchronizationContext.Post will not be used in this case.

In the absence of synchronization context, an await continuation will be executed synchronously on the same thread the antecedent task has completed on.

This is how it is different from your ContinueWith with TaskContinuationOptions.ExecuteSynchronously implementation, which doesn't care at all about the synchronization context of either initial thread or completion thread, and always executes the continuation synchronously (there are exceptions to this behavior, nonetheless).

You can use ConfigureAwait(continueOnCapturedContext: false) to get closer to the desired behavior, but its semantic is still different from TaskContinuationOptions.ExecuteSynchronously. In fact, it instructs the scheduler to not run a continuation on a thread with any synchronization context, so you may experience situations where ConfigureAwait(false) pushes the continuation to thread pool, while you might have been expecting a synchronous execution.

Also related: Revisiting Task.ConfigureAwait(continueOnCapturedContext: false).

这篇关于异步/伺机与手工制作的延续:是ExecuteSynchronously巧妙地利用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆