【问题标题】:"Iterating" over an async method“迭代”异步方法
【发布时间】:2011-12-08 14:56:52
【问题描述】:

关于异步 CTP 的几个相关问题:

  • 我可以使用GetEnumerator() 和枚举器方法MoveNext()Current() 来迭代一个迭代器块(一个IEnumerable<T> yield-returning T)。 async 方法的模拟是什么?非async 调用方法如何接收和处理任何awaited 项目然后ContinueWith()?你能提供一个简短的例子吗?我只是没看到。

  • 此外,在以下示例中的async 方法中,MyAwaitable 有一个GetAwaiter() 方法。如果GetAwaiter() 返回stringTHuh 不是string,编译器不会抱怨。 THuhGetAwaiter() 之间存在哪些类型约束/期望?

    async Task<THuh> DoSomething()
    {
         var x = await new MyAwaitable("Foo");
    
         var y = await new MyAwaitable("Bar");
    
         return null;
    } 
    
  • 请解释 C# 规范草案的以下行。 async Task&lt;T&gt; 方法应该是 returndefault(T) 吗?我看到一些似乎不遵循此规则的示例 - 返回值似乎可以达到,并且该值是非默认值。这个值是不可访问的吗?如果是这样,为什么会出现尴尬的无法访问的 return 语句?

在一些T的返回类型为Task&lt;T&gt;的异步函数中, return 语句必须有一个隐含的表达式 可转换为T,并且body的端点必须不可到达。

  • 规范说“GetAwaiter、IsCompleted、OnCompleted 和 GetResult 的所有内容都是“非阻塞”的”——那么应该在什么方法中定义(可能)长时间运行的操作?

谢谢!

【问题讨论】:

    标签: pattern-matching duck-typing async-ctp c#-5.0


    【解决方案1】:

    我正在尝试使用迭代器块转换我所做的(尴尬但成功)的延续传递,并将其转换为使用异步。我想我正在以新的方式战斗。了解这种变化就像撕开 3 英寸宽的魔术贴条。

    我能理解您的这种感受。我不鼓励人们尝试从迭代器块中构建 CPS,因为无论迭代器和 CPS 有什么共同的底层机制,它确实不太合适。迭代器块设计用于快速制作将数据结构转换为序列或将序列转换为不同序列的方法;它们的设计目的不是为了解决 call-with-current-continuation 的一般问题。

    就此而言,async/await 也不是精确地调用当前继续,尽管它显然更接近一个数量级。 Async/await 旨在使基于任务的异步更容易;它通过将代码重写为延续传递样式的形式来实现这一点是一个实现细节。

    我在相关主题上写的这个答案可能会有所帮助:

    How could the new async feature in c# 5.0 be implemented with call/cc?

    我怀疑您遇到的概念问题是在迭代器样式的异步中,“协调器”——确定迭代器块何时从中断处恢复的东西——是 你的 代码。您编写一些代码并决定何时调用 MoveNext 来抽取迭代器。使用基于任务的异步,其他一些代码可以为您做到这一点。当一个任务完成时,它很有可能将该事实发布到某个消息队列中,然后当消息队列被抽出时,结果会激活延续。您的代码中没有明确的“MoveNext”可供您指出;相反,任务已经完成并且知道它自己的延续这一事实足以确保将延续放入工作队列以供最终执行。

    如果您有更多问题,我鼓励您将它们发布在 SO 和/或异步论坛上。

    【讨论】:

    • 这是一个了不起的答案,你把它钉牢了——那是我的概念脱节。 1 这是对的吗?:如果我编写自己的(等待者和)等待者,我可以控制何时IsComplete 并在MyAwaiter.SetOutcome() 中使用OnComplete(Action) 调用框架注册的任何延续。我可以使用自定义TaskScheduler 来编写确定性的单文件(单线程?)调度策略。 2(在哪里)放弃延续树有多容易(空闲时)?我猜自定义TaskScheduler 可以选择不再安排。有什么后果吗?
    • 嗨 Eric,对上面的 1 和 2 有什么想法吗?
    【解决方案2】:

    在您的DoSomething 示例中,编译器没有抱怨,因为您的MyAwaitable 的GetResult 方法的类型与THuh 无关。与THuh 相关的语句是return null;。 null 文字可以隐式转换为THuh,所以一切都很好。

    类似于awaitIEnumerable 关键字是foreachawait 需要符合特定模式的类型,foreach 也是如此。一种是消费可等待类型的机制,另一种是消费可枚举类型的机制。

    另一方面,迭代器块(yield returnyield break)是定义可枚举类型的机制(通过编写方法而不是显式声明类型)。这里的比喻是async 关键字。

    为了详细说明asyncyield return 之间的类比,请注意返回IEnumerable&lt;int&gt; 的迭代器块可以包含语句yield return 42;,类似地,返回Task&lt;int&gt; 的异步方法可以包含语句@987654346 @。请注意,在这两种情况下,返回表达式的类型不是方法的返回类型,而是方法返回类型的 type 参数


    如果您还没有这样做,那么您真的应该阅读 Eric Lippert 关于以下主题的博客:

    http://blogs.msdn.com/b/ericlippert/archive/tags/Async/

    http://blogs.msdn.com/b/ericlippert/archive/tags/Iterators/

    此外,如果您对这个概念是新的(就像对我一样),关于 Async 系列以外的延续传递风格的帖子可能会很有用:

    http://blogs.msdn.com/b/ericlippert/archive/tags/continuation+passing+style/


    最后,例如,请参阅Eric's blog post linking to his MSDN article and related articles in the same issue 和 Bill Wagner 在http://msdn.microsoft.com/en-us/vstudio/hh533273 的后续文章

    编辑

    我看到一些示例似乎不遵循此规则 - 返回值似乎是可访问的,并且该值是非默认值。这个值是不可访问的吗?如果是这样,为什么会出现尴尬的无法访问的 return 语句?

    “body 的端点必须不可到达”这句话的意思是你必须有一个 return 语句。主体的端点位于 return 语句之后,并且通过 return 语句变得不可访问。使用普通 int 返回方法的示例:

    public int Main()
    {
        Console.WriteLine("X");
        //after this comment is the reachable end point of the body; this method therefore won't compile.
    }
    
    public int Main()
    {
        Console.WriteLine("X");
        return 0;
        //anything after the return statement is unreachable, including the end point of the body; this method therefore will compile.
    }
    

    编辑 2

    这是一个计算字符串后半部分的等待器的简短示例。该示例传递了一个将结果打印到控制台的延续。这不是线程安全的!

    public static class StringExtensions
    {
        public static SubstringAwaiter GetAwaiter(this string s)
        {
            return new SubstringAwaiter(s, s.Length / 2, s.Length - s.Length / 2);
        }
    }
    
    public class SubstringAwaiter
    {
        private readonly string _value;
        private readonly int _start;
        private readonly int _length;
        private string _result;
        private Action _continuation;
    
        public SubstringAwaiter(string value, int start, int length)
        {
            _value = value;
            _start = start;
            _length = length;
        }
    
        public bool IsCompleted { get; private set; }
        public void OnCompleted(Action callback)
        {
            if (callback == null)
                return;
    
            _continuation += callback;
        }
        public string GetResult()
        {
            if (!IsCompleted)
                throw new InvalidOperationException();
            return _result;
        }
        public void Execute()
        {
            _result = _value.Substring(_start, _length);
            IsCompleted = true;
            if (_continuation != null)
                _continuation();
        }
    }
    
    public class Program
    {
        public static void Main()
        {
            var awaiter = "HelloWorld".GetAwaiter();
            awaiter.OnCompleted(() => Console.WriteLine(awaiter.GetResult()));
            awaiter.Execute();
        }
    }
    

    【讨论】:

    • 感谢您的回复。 GetEnumerator()MoveNext()Current() 的类比是什么?我认为您不能在本身不是async 的方法中使用await。如何以非常可控的方式从非异步方法调用异步方法,以便我可以访问所有 awaited 项目进行处理?我认为与 DoSomething().GetAwaiter() 有关,但我不明白如何使用它。当我尝试运行 var doSomething = DoSomething(); 时,DoSomething 会一直运行到第一次等待,但永远不会继续,并且永远不会调用等待者的 GetResult()。
    • GetEnumerator 的类比是GetAwaiter。 { MoveNext 和 Current } 的类比是 { IsCompleted、OnCompleted 和 GetResult }。请参阅 Marc Gravell 的博客:marcgravell.blogspot.com/2011/04/musings-on-async.html
    • 你能举一个简短的例子来调用异步并拦截等待和ContineWith()ing吗? Gravell 博客所附的代码示例对于这个看似简单的案例来说似乎比必要的复杂得多。
    • @uosɐſ 很公平;我还加了一点解释“无法到达的终点”
    • 这也是一个非常好的答案。谢谢!
    猜你喜欢
    • 2014-02-04
    • 2021-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-10
    • 1970-01-01
    • 2016-07-05
    • 1970-01-01
    相关资源
    最近更新 更多