这种在C#中使用async/await的方法之前有人发现过吗?

5

在之前的stackoverflow问题中,涉及到了async / await,我认为await比市场的宣传更具有通用性和强大性。它似乎是构建计算表达式的通用方法,就像F#中一样。所以经过一番努力后,我写出了以下成功执行的代码。

    using FluentAssertions;
    using System.Collections.Generic;

    namespace EnumerableViaAwait.Specs
    {
        [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestClass]
        public class MyTestClass
        {
            public IEnumerable<int> Numbers()
            {
                return EnumeratorMonad.Build<int>(async Yield =>
                {
                    await Yield(11);
                    await Yield(22);
                    await Yield(33);
                });
            }

            [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
            public void TestEnum()
            {
                var v = Numbers();
                var e = v.GetEnumerator();

                int[] expected = { 11, 22, 33 };

                Numbers().Should().ContainInOrder(expected);

            }

        }
    }

现在仔细注意这里正在发生的事情。我没有构建一个响应式可观察对象,而是构建了一个IEnumerable。它是一个严格的拉取系统。我可以很愉快地编写代码。

    foreach item in Numbers(){
            Console.WriteLine(item);
    }

并且它将会打印输出。
    11
    22
    33

这很有趣,因为系统并不是严格的异步的,但我正在滥用等待框架和“等待任何东西”的能力,如此所述。问题如下:http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx.The
  1. 这种滥用 await/async 可以走多远?
  2. 这个模式是否像 F# 计算表达式一样强大?
  3. 传统的 IEnumerator/Yield 模式只是语法糖,完全等同于这个模式吗?

实现该模式的代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Text;
    using System.Threading.Tasks;

    namespace EnumerableViaAwait
    {

        public class EnumeratorMonad<T> : IEnumerable<T>, IEnumerator<T>
        {
            public class Yield
            {
                private EnumeratorMonad<T> _Monad;

                public Yield(EnumeratorMonad<T> monad)
                {
                    _Monad = monad;
                }

                public YieldAwaiter GetAwaiter()
                {
                    return new YieldAwaiter(_Monad);
                }
            }

            public class YieldAwaiter : INotifyCompletion
            {
                EnumeratorMonad<T> _Monad;

                public YieldAwaiter(EnumeratorMonad<T> monad)
                {
                    _Monad = monad;
                }

                public bool IsCompleted
                {
                    get { return _Monad.IsCompleted(); }
                }

                public void GetResult()
                { }

                public void OnCompleted(Action continuation)
                {
                    _Monad.Next = continuation; 
                }

            }

            private bool Completed { get; set; }

            public EnumeratorMonad()
            {
                Completed = false;
            }

            public bool IsCompleted()
            {
                return Completed;
            }

            public void Build(Func<Func<T, Yield>, Task> action)
            {
                Func<T, Yield> yielder = (T value) => { 
                    _Current = value;
                    return new Yield(this);
                };
                Next = async () => { 
                    await action(yielder);
                    Completed = true;
                };
            }

            private T _Current;
            public T Current
            {
                get { return _Current; }
            }

            public void Dispose()
            {
            }

            object System.Collections.IEnumerator.Current
            {
                get { return _Current; }
            }


            Action Next;
            public bool MoveNext()
            {
                if (!Completed )
                {
                    Next();
                }
                return !Completed;
            }

            public void Reset()
            {
                throw new NotImplementedException();
            }



            IEnumerator<T> IEnumerable<T>.GetEnumerator()
            {
                return this;
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this;
            }
        }

        public class EnumeratorMonad{
            public static EnumeratorMonad<T> Build<T>(Func<Func<T, EnumeratorMonad<T>.Yield>, Task> action)
            {
                var monad = new EnumeratorMonad<T>();
                monad.Build(action);
                return monad;
            }
        }

}


你的问题是什么? - riwalk
问题在中间。我猜你读了中间,对吗? - bradgonesurfing
对于发现这个特别有趣的事情,点个赞吧。 :) - Leonard
1
@bradgonesurfing,一行开头的4个空格会使其格式化为代码。你知道这个...对吧? - riwalk
2
你可能会对 Jon Skeet 的 eduasync 帖子 感兴趣。 - Stephen Cleary
嗨,Stephen,你的评论就是我在寻找的答案。Jon Skeet确实在使用async/await方面做了一些不好的事情。如果你把那个链接作为答案,我会将其标记为被接受的 :) - bradgonesurfing
1个回答

5
yield returnawait/async只是协程的不同形式。您已经表明,可以使用await/async来实现yield return(基本上),而且我也不会感到惊讶,如果反过来也是可能的。我相信它们以非常相似的方式实现。

在实践中,当然,我不会使用await/async进行迭代,因为yield return更简单和更清晰。

所以,

  1. 您可能会将此“滥用”推向极致。
  2. 对F#不够熟悉,无法回答。
  3. 否,但我IRC功能实现方式基本相同。

我想要弄清楚的是,async/await是否是“专用”的,还是它们完全通用,可以用于实现比IEnumerator或Async更有趣的工作流程。我认为你不能使用yield return来实现async/await,因为yield return不返回lvalue。 - bradgonesurfing
你可能是对的,以另一种方式可能无法获得如此漂亮的语法。至于你可以用它们做什么——可能任何事情,但最好的方法是尝试一下。 - Thom Smith
1
我接下来要尝试使用Maybe单子。我认为我可以编写类似Do符号的东西。如果返回任何Maybe::Nothing,序列应该提前终止。这不是非常漂亮,但对于解析XML可能很有用。 - bradgonesurfing
这种模式的一个巨大优点 - 可能弥补了可读性的降低 - 是它允许您创建匿名可枚举生成器(如给定示例代码中所示),而 yield return 关键字不允许。 - Erik Forbes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接