为了账号安全,请及时绑定邮箱和手机立即绑定

如何在.NET中产生并等待实现控制流?

如何在.NET中产生并等待实现控制流?

慕斯709654 2019-12-10 10:48:42
据我了解yield,如果从迭代器块内部使用该关键字,它将控制流返回到调用代码,并且当再次调用该迭代器时,它将从中断的地方开始。同样,await不仅要等待被调用方,而且还会将控制权返回给调用方,仅在调用方awaits方法时从中断处接管。换句话说,没有线程,异步和等待的“并发性”是由聪明的控制流引起的错觉,其细节被语法隐藏了。现在,我是一名前汇编程序员,并且对指令指针,堆栈等非常熟悉,并且了解了正常的控制流程(子例程,递归,循环,分支)的工作方式。但是这些新结构-我不明白。当await到达,如何运行时知道什么是一段代码下一步应该执行?它如何知道何时可以从上次中断的地方恢复,以及如何记住在哪里?当前调用堆栈发生了什么,是否以某种方式保存了它?如果调用方法在此之前进行其他方法调用await怎么办-为什么堆栈不被覆盖?在异常和堆栈展开的情况下,运行时到底将如何处理所有这些问题?何时yield到达,运行时如何跟踪应该拾取的点?迭代器状态如何保存?
查看完整描述

3 回答

?
梵蒂冈之花

TA贡献1900条经验 获得超5个赞

yield 是两者中比较容易的一个,所以让我们检查一下。


说我们有:


public IEnumerable<int> CountToTen()

{

  for (int i = 1; i <= 10; ++i)

  {

    yield return i;

  }

}

这被编译一个位,如果我们想这样写的:


// Deliberately use name that isn't valid C# to not clash with anything

private class <CountToTen> : IEnumerator<int>, IEnumerable<int>

{

    private int _i;

    private int _current;

    private int _state;

    private int _initialThreadId = CurrentManagedThreadId;


    public IEnumerator<CountToTen> GetEnumerator()

    {

        // Use self if never ran and same thread (so safe)

        // otherwise create a new object.

        if (_state != 0 || _initialThreadId != CurrentManagedThreadId)

        {

            return new <CountToTen>();

        }


        _state = 1;

        return this;

    }


    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();


    public int Current => _current;


    object IEnumerator.Current => Current;


    public bool MoveNext()

    {

        switch(_state)

        {

            case 1:

                _i = 1;

                _current = i;

                _state = 2;

                return true;

            case 2:

                ++_i;

                if (_i <= 10)

                {

                    _current = _i;

                    return true;

                }

                break;

        }

        _state = -1;

        return false;

    }


    public void Dispose()

    {

      // if the yield-using method had a `using` it would

      // be translated into something happening here.

    }


    public void Reset()

    {

        throw new NotSupportedException();

    }

}

所以,还不如一个手写的执行效率IEnumerable<int>和IEnumerator<int>(例如,我们可能不会有一个单独的浪费_state,_i而且_current在这种情况下),但不坏(的伎俩再利用自身安全情况下这样做,而不是创建一个新的对象是好的),并且可以扩展以处理非常复杂的yield使用方法。


当然,因为


foreach(var a in b)

{

  DoSomething(a);

}

是相同的:


using(var en = b.GetEnumerator())

{

  while(en.MoveNext())

  {

     var a = en.Current;

     DoSomething(a);

  }

}

然后,生成的MoveNext()被重复调用。


这种async情况几乎是相同的原理,但是有一些额外的复杂性。重用另一个答案代码中的示例,例如:


private async Task LoopAsync()

{

    int count = 0;

    while(count < 5)

    {

       await SomeNetworkCallAsync();

       count++;

    }

}

产生如下代码:


private struct LoopAsyncStateMachine : IAsyncStateMachine

{

  public int _state;

  public AsyncTaskMethodBuilder _builder;

  public TestAsync _this;

  public int _count;

  private TaskAwaiter _awaiter;

  void IAsyncStateMachine.MoveNext()

  {

    try

    {

      if (_state != 0)

      {

        _count = 0;

        goto afterSetup;

      }

      TaskAwaiter awaiter = _awaiter;

      _awaiter = default(TaskAwaiter);

      _state = -1;

    loopBack:

      awaiter.GetResult();

      awaiter = default(TaskAwaiter);

      _count++;

    afterSetup:

      if (_count < 5)

      {

        awaiter = _this.SomeNetworkCallAsync().GetAwaiter();

        if (!awaiter.IsCompleted)

        {

          _state = 0;

          _awaiter = awaiter;

          _builder.AwaitUnsafeOnCompleted<TaskAwaiter, TestAsync.LoopAsyncStateMachine>(ref awaiter, ref this);

          return;

        }

        goto loopBack;

      }

      _state = -2;

      _builder.SetResult();

    }

    catch (Exception exception)

    {

      _state = -2;

      _builder.SetException(exception);

      return;

    }

  }

  [DebuggerHidden]

  void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)

  {

    _builder.SetStateMachine(param0);

  }

}


public Task LoopAsync()

{

  LoopAsyncStateMachine stateMachine = new LoopAsyncStateMachine();

  stateMachine._this = this;

  AsyncTaskMethodBuilder builder = AsyncTaskMethodBuilder.Create();

  stateMachine._builder = builder;

  stateMachine._state = -1;

  builder.Start(ref stateMachine);

  return builder.Task;

}

它比较复杂,但是基本原理非常相似。最主要的复杂之处在于现在GetAwaiter()正在使用它。如果awaiter.IsCompleted检查了任何时间,则true由于任务awaited已经完成(例如,它可以同步返回),该方法将返回,然后该方法将继续遍历状态,否则将其自身设置为对等待者的回调。


究竟发生什么取决于等待者,包括触发回调的原因(例如异步I / O完成,在线程上运行的任务完成)以及对编组到特定线程或在线程池线程上运行有什么要求,可能需要也可能不需要原始调用的上下文,依此类推。无论该等待者中的内容是什么,都会调用MoveNext,它将继续进行下一个工作(直到下一个工作await),或者完成并返回,在这种情况下Task,实现的对象将完成。


查看完整回答
反对 回复 2019-12-10
  • 3 回答
  • 0 关注
  • 481 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信