【问题标题】:Confusion about lua corountine's resume and yield function关于lua corountine的resume和yield函数的困惑
【发布时间】:2016-10-30 09:35:38
【问题描述】:

我正在通过这个video tutorial学习lua,它有这段代码:

co = coroutine.create(function()
    for i=1,5 do
      print(coroutine.yield(i))
    end
  end)

print(coroutine.resume(co,1,2))
print(coroutine.resume(co,3,4))
print(coroutine.resume(co,5,6))
print(coroutine.resume(co,7,8))
print(coroutine.resume(co,9,10))
print(coroutine.resume(co,11,12))

输出是这样的:

true    1
3   4
true    2
5   6
true    3
7   8
true    4
9   10
true    5
11  12
true

但是我不明白yield和resume如何相互传递参数以及为什么yield不输出resume传递给它的第一个1,2,有人可以解释一下吗?谢谢

【问题讨论】:

    标签: lua coroutine


    【解决方案1】:

    普通 Lua 函数有一个入口(传入参数)和一个出口(传出返回值):

    local function f( a, b )
      print( "arguments", a, b )
      return "I'm", "done"
    end
    
    print( "f returned", f( 1, 2 ) )
    --> arguments       1       2
    --> f returned      I'm     done
    

    参数通过将它们放在括号内绑定到参数名称(局部变量),作为return语句的一部分列出的返回值可以通过将函数调用表达式放在右侧来检索赋值语句,或在更大的表达式内部(例如另一个函数调用)。

    还有其他调用函数的方法。例如。 pcall() 调用一个函数并捕获可能在内部引发的任何运行时错误。参数通过将它们作为参数放入pcall() 函数调用(就在函数本身之后)来传递。 pcall() 还附加了一个返回值,指示函数是正常退出还是通过错误退出。被调用函数的内部没有变化。

    print( "f returned", pcall( f, 1, 2 ) )
    --> arguments       1       2
    --> f returned      true    I'm     done
    

    您可以使用coroutine.resume() 而不是pcall() 来调用协程的主函数。参数的传递方式,额外的返回值保持不变:

    local th = coroutine.create( f )
    print( "f returns", coroutine.resume( th, 1, 2 ) )
    --> arguments       1       2
    --> f returns       true    I'm     done
    

    但是使用协程,您可以获得另一种(暂时)退出函数的方法:coroutine.yield()。您可以通过coroutine.yield() 将值作为参数放入yield() 函数调用中来传递值。这些值可以作为coroutine.resume() 调用的返回值在外部检索,而不是正常的返回值。

    但是,您可以通过再次调用 coroutine.resume() 重新进入生成的协程。协程从中断处继续,传递给coroutine.resume() 的额外值可用作之前暂停协程的yield() 函数调用的返回值。

    local function g( a, b )
      print( "arguments", a, b )
      local c, d = coroutine.yield( "a" )
      print( "yield returned", c, d )
      return "I'm", "done"
    end
    
    local th = coroutine.create( g )
    print( "g yielded", coroutine.resume( th, 1, 2 ) )
    print( "g returned", coroutine.resume( th, 3, 4 ) )
    --> arguments       1       2
    --> g yielded       true    a
    --> yield returned  3       4
    --> g returned      true    I'm     done
    

    注意,yield 不需要直接在协程的主函数中,它可以在嵌套函数调用中。执行会跳回到最初(重新)启动协程的coroutine.resume()

    现在回答您的问题,为什么第一个 resume() 中的 1, 2 没有出现在您的输出中:您的协程主函数没有列出任何参数,因此忽略了传递给它的所有参数(在第一个函数上入口)。同样,由于您的 main 函数不返回任何返回值,因此最后一个 resume() 不返回任何额外的返回值,除了表示成功执行的 true

    【讨论】:

      【解决方案2】:
      co = coroutine.create(function()
          for i=1,5 do
            print(coroutine.yield(i))
          end
        end)
      

      我们第一次启动协程时使用:

      print(coroutine.resume(co,1,2))
      

      它将运行到第一个收益。我们的第一个 resume 调用将返回 true 和 yield 的参数(这里 i = 1),它解释了第一行输出。

      我们的协程现在被挂起。一旦我们再次调用 resume:

      print(coroutine.resume(co,3,4))
      

      您的第一个收益最终返回,并且您当前简历的参数 (3,4) 将被打印出来。 for 循环第二次迭代开始,coroutine.yield(2) 被调用,挂起协程,这将再次使你的最后一个恢复返回 true,2 等等

      所以实际上在你的例子中 coroutine.resume(co) 对于第一次调用就足够了,因为任何进一步的参数都会丢失。

      【讨论】:

      • 感谢您的回答。这是否意味着 yield 函数实际上在返回之前将其参数以及 true 传递给 resume 函数?
      • 如果简历没有错误,则返回no true。
      【解决方案3】:

      我们看到这种行为的原因很微妙,但它与“进入”和“退出”yield 语句的不匹配有关。它还与在匿名函数中调用 printyield 的顺序有关。

      让我们想象一下print(coroutine.yield(i)) 的执行与迭代的关系图。

      在第一次迭代中,我们让coroutine.resume12 传递给协程。这是原点,所以我们不是以前的yield 接听,而是匿名函数本身的原始调用。 yield 在 print 内部被调用,返回 i=1 但未调用 print。函数退出。

      接下来是函数暂停一段时间,然后我们看到下一个coroutine.resume恢复。这个resume通过34。函数在最后获得 yield。还记得print 函数没有被调用,因为第一个yield 首先被调用并退出了程序?好吧,在print 内执行返回,所以现在调用print,但这次返回34,因为这些是要传输的最新值。函数再次重复,在print之前调用yield,返回i=2

      如果我们继续迭代,我们将更明确地了解为什么我们没有看到 12 的背后模式。我们的第一次迭代是“退出”收益率,未与相应的“进入收益率”配对。这对应于协程co的第一次执行。

      我们可能期望最后一个 yield 也未配对,但不同之处在于我们将有一个未配对的“进入”yield,而不是未配对的“退出”yield。这是因为循环已经完成。这解释了为什么我们看到 11 12 后跟 true 后没有“退出”yield 返回。

      这种情况与内部 for 循环的奇偶校验(偶数/奇数)无关。唯一重要的是resumeyield 对以及它们的处理方式。你必须明白yield 不会在第一次调用resume 时返回函数内的值,这首先用于调用协程内的函数。

      【讨论】:

        猜你喜欢
        • 2018-04-26
        • 2015-04-08
        • 2020-07-05
        • 2014-03-22
        • 2020-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多