首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >异步CTP和“最终”

异步CTP和“最终”
EN

Stack Overflow用户
提问于 2011-02-18 02:07:48
回答 1查看 2.5K关注 0票数 19

代码如下:

代码语言:javascript
复制
static class AsyncFinally
{
    static async Task<int> Func( int n )
    {
        try
        {
            Console.WriteLine( "    Func: Begin #{0}", n );
            await TaskEx.Delay( 100 );
            Console.WriteLine( "    Func: End #{0}", n );
            return 0;
        }
        finally
        {
            Console.WriteLine( "    Func: Finally #{0}", n );
        }
    }

    static async Task Consumer()
    {
        for ( int i = 1; i <= 2; i++ )
        {
            Console.WriteLine( "Consumer: before await #{0}", i );
            int u = await Func( i );
            Console.WriteLine( "Consumer: after await #{0}", i );
        }
        Console.WriteLine( "Consumer: after the loop" );
    }

    public static void AsyncTest()
    {
        Task t = TaskEx.RunEx( Consumer );
        t.Wait();
        Console.WriteLine( "After the wait" );
    }
}

下面是输出:

代码语言:javascript
复制
Consumer: before await #1
    Func: Begin #1
    Func: End #1
Consumer: after await #1
Consumer: before await #2
    Func: Begin #2
    Func: Finally #1
    Func: End #2
Consumer: after await #2
Consumer: after the loop
    Func: Finally #2
After the wait

正如您所看到的,finally块的执行时间比您预期的要晚得多。

有什么解决方法吗?

提前感谢!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2011-02-18 18:22:16

这是一个很好的捕获-我同意这里的CTP中实际上存在一个bug。我深入研究了一下,这是怎么回事:

这是异步编译器转换的CTP实现,以及来自.NET 4.0+的任务并行库的现有行为的组合。以下是起作用的因素:

来自源代码的最终主体被翻译成真正的-

  1. 主体的一部分。出于许多原因,这是可取的,其中之一是我们可以让CLR执行它,而不需要额外的时间来捕获/重新抛出异常。这也在一定程度上简化了我们的代码生成--更简单的代码生成在编译后会产生更小的二进制文件,这肯定是我们的许多客户所希望的。:)
  2. Func(int n)方法的总体Task是一个真正的第三方逻辑任务。当你在await中输入Consumer()时,Consumer()方法的其余部分实际上是作为完成从Func(int n).
  3. The返回的Task的延续安装的。CTP编译器转换异步方法的方式是在实际返回之前将return映射到SetResult(...)调用。TPL归结为对TaskCompletionSource<>.TrySetResult.
  4. TaskCompletionSource<>.TrySetResult的调用,表示SetResult(...)任务完成。立即使它的继续发生在“某个时候”。这个“某一时刻”可能意味着在另一个线程上,或者在某些情况下,第三方公共关系很聪明,会说“嗯,我也可以现在就在这个线程上调用它”。
  5. Func(int n)的总体Task在最终运行之前在技术上变得“完成”。这意味着等待异步方法的代码可以在并行线程中运行,甚至可以在finally块之前运行。

考虑到总体Task应该表示方法的异步状态,基本上它不应该被标记为完成,直到至少所有用户提供的代码都按照语言设计执行了。我将向Anders、语言设计团队和编译器开发人员提出这一点,以了解这一点。

表现形式/严重程度的范围:

在WPF或WinForms的情况下,您通常不会受到这种情况的影响,因为您正在进行某种托管消息循环。原因是Task实现上的await遵循SynchronizationContext。这会导致异步延续在预先存在的消息循环上排队,以便在同一线程上运行。您可以通过以下方式更改代码以运行Consumer()来验证这一点:

代码语言:javascript
复制
    DispatcherFrame frame = new DispatcherFrame(exitWhenRequested: true);
    Action asyncAction = async () => {
        await Consumer();
        frame.Continue = false;
    };
    Dispatcher.CurrentDispatcher.BeginInvoke(asyncAction);
    Dispatcher.PushFrame(frame);

在WPF消息循环的上下文中运行后,输出将按预期显示:

代码语言:javascript
复制
Consumer: before await #1
    Func: Begin #1
    Func: End #1
    Func: Finally #1
Consumer: after await #1
Consumer: before await #2
    Func: Begin #2
    Func: End #2
    Func: Finally #2
Consumer: after await #2
Consumer: after the loop
After the wait

解决方法:

遗憾的是,变通方法意味着更改代码,使其不在try/finally块中使用return语句。我知道这真的意味着你在代码流中失去了很多优雅。你可以使用async helper方法或者helper lambda来解决这个问题。就我个人而言,我更喜欢helper-lambdas,因为它自动关闭包含方法的本地变量/参数,并使您的相关代码更接近。

助手Lambda方法:

代码语言:javascript
复制
static async Task<int> Func( int n )
{
    int result;
    try
    {
        Func<Task<int>> helperLambda = async() => {
            Console.WriteLine( "    Func: Begin #{0}", n );
            await TaskEx.Delay( 100 );
            Console.WriteLine( "    Func: End #{0}", n );        
            return 0;
        };
        result = await helperLambda();
    }
    finally
    {
        Console.WriteLine( "    Func: Finally #{0}", n );
    }
    // since Func(...)'s return statement is outside the try/finally,
    // the finally body is certain to execute first, even in face of this bug.
    return result;
}

帮助方法方法:

代码语言:javascript
复制
static async Task<int> Func(int n)
{
    int result;
    try
    {
        result = await HelperMethod(n);
    }
    finally
    {
        Console.WriteLine("    Func: Finally #{0}", n);
    }
    // since Func(...)'s return statement is outside the try/finally,
    // the finally body is certain to execute first, even in face of this bug.
    return result;
}

static async Task<int> HelperMethod(int n)
{
    Console.WriteLine("    Func: Begin #{0}", n);
    await TaskEx.Delay(100);
    Console.WriteLine("    Func: End #{0}", n);
    return 0;
}

作为一个厚颜无耻的插头:我们正在微软的语言空间招聘,并一直在寻找优秀的人才。博客条目here,包含空缺职位的完整列表:)

票数 14
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5032784

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档