如何在C#中使用协程?

10 投票
7 回答
2669 浏览
提问于 2025-04-15 20:42

在Python中,yield这个关键词可以在“推”和“拉”这两种情况下使用。我知道在C#中怎么实现“拉”的情况,但我该如何实现“推”的呢?我在这里贴出我想在C#中复制的Python代码:

def coroutine(func):
  def start(*args,**kwargs):
    cr = func(*args,**kwargs)
    cr.next()
    return cr
  return start

@coroutine
def grep(pattern):
  print "Looking for %s" % pattern
  try:
    while True:
      line = (yield)
      if pattern in line:
        print line,
  except GeneratorExit:
    print "Going away. Goodbye"

7 个回答

3

谢谢你,@NickLarsen,你让我想起了微软新推出的东西,IObservable接口。

链接 http://msdn.microsoft.com/en-us/library/dd783449(VS.100).aspx

7

C#并没有真正意义上的协程。真正的协程是指它有自己的栈,也就是说,它可以调用其他方法,而这些方法可以“返回”值。实现真正的协程需要对栈进行一些复杂的操作,甚至可能需要在堆上分配栈帧(这些是存放局部变量的隐藏结构)。虽然有些编程语言可以做到这一点(比如Scheme),但要做到正确其实挺难的。而且,很多程序员觉得这个功能很难理解。

真正的协程可以用线程来模拟。每个线程都有自己的栈。在协程的设置中,两个线程(一个是最初调用的线程,另一个是协程的线程)会交替控制,它们不会同时运行。这里的“返回”机制其实是两个线程之间的交换,因此开销比较大(需要同步、经过操作系统内核和调度器的往返等)。另外,这样做也容易出现内存泄漏(协程必须明确“停止”,否则等待的线程会一直卡在那里)。所以,这种做法很少被采用。

C#提供了一种简化版的协程功能,叫做迭代器。C#编译器会自动把迭代器的代码转换成一个特定的状态类,局部变量会变成类的字段。在虚拟机层面上,返回值就像是一个普通的return。只要“返回”是在迭代器代码内部进行的,而不是在迭代器调用的其他方法中进行的,这样的做法是可行的。C#的迭代器已经覆盖了很多使用场景,而C#的设计者不愿意进一步实现更复杂的功能,比如继续执行。有些人开玩笑说,如果实现了完整的继续执行功能,C#就无法像它的死对头Java那样高效(高效的继续执行是可行的,但这需要对垃圾回收和即时编译器进行相当多的工作)。

14

如果你想要的是一种“可观察集合”——也就是说,这种集合会主动把结果推送给你,而不是让你自己去拉取结果——那么你可能需要看看反应式框架的扩展。这里有一篇相关的文章:

http://www.infoq.com/news/2009/07/Reactive-Framework-LINQ-Events

正如你所提到的,如果你有协程的话,可以很容易地构建“推送”和“拉取”风格的迭代器。(或者,正如托马斯所说,你也可以用继续执行来构建它们。)在当前版本的C#中,我们并没有真正的协程(或者继续执行)。不过,我们非常关注用户在异步编程中遇到的困难。

实现基于纤程的协程作为一种语言的基本特性,可能是让异步编程变得更简单的一种方法,但这只是我们目前正在研究的众多想法中的一种。如果你有一个非常好的场景,能说明协程比其他任何东西(包括反应式框架)做得更好,我很想听听更多的细节。我们越了解人们在异步编程中遇到的真实问题,就越有可能找到好的解决方案。谢谢!

更新:我们最近宣布将在下一个版本的C#和VB中添加类似协程的异步控制流。你可以通过我们的社区技术预览版自己试试,下载链接在这里

撰写回答