为什么 Enumerator.MoveNext 在与 using 和 async-await 一起使用时不能按我的预期工作?

2024-01-19

我想通过一个枚举List<int>并调用异步方法。

如果我这样做:

public async Task NotWorking() {
  var list = new List<int> {1, 2, 3};

  using (var enumerator = list.GetEnumerator()) {
    Trace.WriteLine(enumerator.MoveNext());
    Trace.WriteLine(enumerator.Current);

    await Task.Delay(100);
  }
}

结果是:

True
0

但我希望它是:

True
1

如果我删除using or the await Task.Delay(100):

public void Working1() {
  var list = new List<int> {1, 2, 3};

  using (var enumerator = list.GetEnumerator()) {
    Trace.WriteLine(enumerator.MoveNext());
    Trace.WriteLine(enumerator.Current);
  }
}

public async Task Working2() {
  var list = new List<int> {1, 2, 3};

  var enumerator = list.GetEnumerator();
  Trace.WriteLine(enumerator.MoveNext());
  Trace.WriteLine(enumerator.Current);

  await Task.Delay(100);
}

输出如预期:

True
1

谁能向我解释这种行为吗?


这是这个问题的不足之处。下面是更长的解释。

  • List<T>.GetEnumerator() https://msdn.microsoft.com/en-us/library/b0yss765(v=vs.110).aspx返回一个结构体,一个值类型。
  • 这个结构是可变的(永远是灾难的根源 https://stackoverflow.com/questions/441309/why-are-mutable-structs-evil)
  • 当。。。的时候using () {}存在时,该结构存储在底层生成类的字段中以处理await part.
  • 打电话时.MoveNext()通过这个字段,从底层对象加载字段值的副本,因此就好像MoveNext读取代码时从未被调用.Current

正如 Marc 在评论中提到的,既然您知道了问题,一个简单的“修复”就是重写代码以显式装箱结构,这将确保可变结构与此代码中各处使用的结构相同,而不是新的副本到处都在变异。

using (IEnumerator<int> enumerator = list.GetEnumerator()) {

那么,会发生什么really here.

The async / await方法的本质对方法做了一些事情。具体来说,整个方法被提升到一个新生成的类上并变成一个状态机。

随处可见await,该方法有点“拆分”,因此该方法必须像这样执行:

  1. 调用初始部分,直到第一个等待
  2. 下一部分必须由MoveNext有点像IEnumerator
  3. 下一部分(如果有的话)以及所有后续部分都由这个处理MoveNext part

This MoveNext方法是在此类上生成的,原始方法中的代码被零碎地放置在其中,以适应方法中的各个序列点。

因此,任何local该方法的变量必须从对此的一次调用中幸存下来MoveNext方法到下一个,并且它们被“提升”到此类作为私有字段。

示例中的类可以非常简单地被重写为这样的:

public class <NotWorking>d__1
{
    private int <>1__state;
    // .. more things
    private List<int>.Enumerator enumerator;

    public void MoveNext()
    {
        switch (<>1__state)
        {
            case 0:
                var list = new List<int> {1, 2, 3};
                enumerator = list.GetEnumerator();
                <>1__state = 1;
                break;

            case 1:
                var dummy1 = enumerator;
                Trace.WriteLine(dummy1.MoveNext());
                var dummy2 = enumerator;
                Trace.WriteLine(dummy2.Current);
                <>1__state = 2;
                break;

这段代码是与正确的代码相差甚远,但足够接近此目的。

这里的问题是第二种情况。由于某种原因,生成的代码将该字段读取为副本,而不是对该字段的引用。因此,调用.MoveNext()在此副本上完成。原始字段值保持原样,所以当.Current读取时,返回原始默认值,在本例中为0.


那么我们来看看这个方法生成的IL。我执行了原来的方法(只改变Trace to Debug) in LINQPad http://linqpad.net因为它能够转储生成的 IL。

我不会在这里发布完整的 IL 代码,但让我们看看枚举器的用法:

Here's var enumerator = list.GetEnumerator():

IL_005E:  ldfld       UserQuery+<NotWorking>d__1.<list>5__2
IL_0063:  callvirt    System.Collections.Generic.List<System.Int32>.GetEnumerator
IL_0068:  stfld       UserQuery+<NotWorking>d__1.<enumerator>5__3

这是调用MoveNext:

IL_007F:  ldarg.0     
IL_0080:  ldfld       UserQuery+<NotWorking>d__1.<enumerator>5__3
IL_0085:  stloc.3     // CS$0$0001
IL_0086:  ldloca.s    03 // CS$0$0001
IL_0088:  call        System.Collections.Generic.List<System.Int32>+Enumerator.MoveNext
IL_008D:  box         System.Boolean
IL_0092:  call        System.Diagnostics.Debug.WriteLine

ldfld https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldfld%28v=vs.110%29.aspx这里读取字段值并将该值压入堆栈。然后这个副本被存储在一个局部变量中.MoveNext()方法,然后通过调用来改变这个局部变量.MoveNext().

由于最终结果现在在此局部变量中,已更新存储回字段中,因此该字段保持原样。


这是一个不同的示例,它使问题“更清晰”,因为枚举器是一个结构体,对我们来说有点隐藏:

async void Main()
{
    await NotWorking();
}

public async Task NotWorking()
{
    using (var evil = new EvilStruct())
    {
        await Task.Delay(100);
        evil.Mutate();
        Debug.WriteLine(evil.Value);
    }
}

public struct EvilStruct : IDisposable
{
    public int Value;
    public void Mutate()
    {
        Value++;
    }

    public void Dispose()
    {
    }
}

这也会输出0.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 Enumerator.MoveNext 在与 using 和 async-await 一起使用时不能按我的预期工作? 的相关文章

随机推荐

  • 完全通过 FIFO 连接到 MySQL 客户端

    在 Bash 脚本中 我想在多个顺序访问中保持 MySQL 会话打开 访问 MySQL 的常见方法是为每个 SQL 命令或命令集打开一个单独的会话 例如 mysql u user e show tables 此方法的限制是那些需要双重事务的
  • Django settings.AUTH_USER_MODEL 在单独的模块中定义

    表达我的事情的正确方式是什么AUTH USER MODEL 我有以下一组 文件夹结构 后端 API 楷模 用户 py user py位于内models folder 在设置 py中 AUTH USER MODEL myapp User IN
  • 有没有办法在 JavaFX LineChart 中断开串联的 2 个点?

    我在 LineChart 上有四个系列 每个系列都包含一定数量的按时间划分的图表 默认情况下 LineChart 连接这些图表 它看起来很难看并且在上下文中没有任何意义 所以我想将它们分开 但保留颜色和图例 换句话说 我想要的是删除两个特定
  • Spring框架中的依赖注入和控制反转是什么?

    依赖注入 和 控制反转 经常被认为是使用 Spring 框架开发 Web 框架的主要优点 如果可能的话 有人可以用一个非常简单的术语解释它是什么吗 Spring 有助于创建松散耦合的应用程序 因为依赖注入 在 Spring 中 对象定义它们
  • C++ 自省技术,类似于 python

    C 中是否有像 Python 中那样的自省技术 例如 我想获取有关特定对象的更多信息 而不需要通过头文件或引用 cpp 引用 我是问了一个正确的问题 还是走错了方向 Update 根据以下答案 这个答案与我的问题相关 如何向 C 应用程序添
  • Blaze:{{#if}} 语句中的逻辑(Not、Or、And...)

    有没有办法在 if 语句中进行逻辑运算 我希望有这样的事情 if A B some html if 我在 blaze 中找不到有关逻辑的文档 所以我猜它不受支持 我只是想确定一下 抱歉问了一个相当愚蠢的问题 正如 Billy Bob 所建议
  • Express.js:如何获取 ip 地址并渲染视图?

    我真的认为这应该很容易 但是当我渲染一个jade模板时 我也想抓取ip地址 我的代码看起来像这样 app js app get index home index js exports home function req res res re
  • C# 中的外部 IP 地址

    在 C 中获取外部 IP 地址的最简单方法是什么 框架内没有内置的方法来执行此操作 因为很难确定外部 公共 IP 地址是什么 当然 这是假设您的 IP 在某个网关后面经过 NAT 一种方法是抓取类似的网站http www whatismyi
  • 解释错误:ISO C++ 禁止声明没有类型的“Personlist”

    我有一个类将处理我之前创建的另一个类的对象数组 效果很好 当我尝试创建列表类的对象时出现问题 这是列表类的标题 ifndef personlistH define personlistH include Person h include
  • 如何从当前月份中选择当前日期

    我想检索当月 1 30 之间的数据 我正在使用 MSACCESS Dbase 来执行此操作 下面是我正在尝试的查询 SELECT count usercategory as category count usercategory FROM
  • VS Code 扩展安全如何处理?

    我已经使用 VS Code 一年左右了 我不知道 VS Code Extension 安全性是如何处理的 我对这样的事情感到震惊 Markdown 预览增强 https marketplace visualstudio com items
  • CRA + React Leaflet:编译失败

    我刚刚开始一个全新的项目create react app并设置react leaflet正如他们的文档所建议的here https react leaflet js org docs start installation 我正在尝试使用这个
  • pyspark:自动填充隐式缺失值

    我有一个数据框 user day amount a 2 10 a 1 14 a 4 5 b 1 4 你看 最大值day是4 最小值是1 我要填0 for amount列中所有用户的所有缺失天数 因此上面的数据框将变为 user day am
  • Safari 和 iOS 上的 Html5(音频)

    我正在开发一款 Web 应用程序 但与 Apple 设备和 PC 上的 Safari 浏览器存在兼容性问题 Html5 音频标签
  • 假人的 Getters \ Setters

    我一直在尝试了解 getter 和 setter 但它没有被理解 我读过JavaScript Getter 和 Setter http ejohn org blog javascript getters and setters and 定义
  • 可靠地验证 JWS 证书链和域

    我正在 Node JS 中编写后端代码来验证来自 Google SafetyNet API 的 JWS 我很惊讶没有为此找到现成的模块 因此我开始使用可用的库来研究 JWS 的一些简单验证 首先 谷歌表示需要执行以下步骤 从 JWS 消息中
  • 如何在Windows服务器上运行html文件中的php代码?

    我已使用 htaccess 文件和以下代码AddType 应用程序 x httpd php html htm 它在本地工作正常 但当我将文件上传到服务器上时 它工作不正常 我的所有页面都有 html 扩展名 您需要添加到 PHP exe 的
  • 递归泛型类型

    是否可以在 C 中定义引用自身的泛型类型 例如 我想定义一个 Dictionary 将其类型保存为 TValue 对于层次结构 Dictionary
  • echo 输出与预期的格式化输出不同

    I m using the following formatting for echo 它应该输出粗体和下划线的文本 但不是将其设置为粗体 而是提供 更亮 的颜色变体 并将带下划线的代码 4 视为青色 从而产生浅青色文本 我在 Window
  • 为什么 Enumerator.MoveNext 在与 using 和 async-await 一起使用时不能按我的预期工作?

    我想通过一个枚举List