【问题标题】:Coroutines in C#C#中的协程
【发布时间】:2011-02-05 15:52:29
【问题描述】:

我正在寻找在 c# 中实现协同例程(用户调度线程)的方法。使用 c++ 时,我使用的是纤维。我在互联网上看到 C# 中不存在光纤。我想获得类似的功能。

在 C# 中实现协程有什么“正确”的方法吗?

我曾想过使用在调度程序线程上获取单个执行互斥锁 + 1 的线程来实现这一点,该线程为每个协程释放此互斥锁。但这似乎非常昂贵(它强制每个协程之间进行上下文切换)

我也看到了 yield 迭代器功能,但据我了解,您不能在内部函数中 yield(仅在原始 ienumerator 函数中)。所以这对我没什么好处。

【问题讨论】:

标签: c# coroutine fiber


【解决方案1】:

我相信对于新的 .NET 4.5\C# 5,async\await 模式应该可以满足您的需求。

async Task<string> DownloadDocument(Uri uri)
{  
  var webClient = new WebClient();
  var doc = await webClient.DownloadStringTaskAsync(url);
  // do some more async work  
  return doc;  
}  

我建议查看http://channel9.msdn.com/Events/TechEd/Australia/Tech-Ed-Australia-2011/DEV411 了解更多信息。这是一个很棒的演示文稿。

http://msdn.microsoft.com/en-us/vstudio/gg316360 也有一些很棒的信息。

如果您使用的是旧版本的 .NET,则可以使用适用于旧版 .NET 的异步 CTP 并获得上线许可证,以便您可以在生产环境中使用它。这是 CTP 的链接http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=9983

如果您不喜欢上述任何一个选项,我相信您可以遵循此处概述的异步迭代器模式。 http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=9983

【讨论】:

    【解决方案2】:

    编辑:您现在可以使用这些:Is there a fiber api in .net?

    我相信你应该看看Reactive Extensions for .NET。例如coroutines can be simulated using iterators and the yield 语句。

    但是,您可能也想阅读此SO question

    【讨论】:

    • Iterator 块从 Rx 2.0 开始不再相关,它现在提供了接受 Task-returning 函数的 Observable.Create 的重载,因此将 observable (call/cc) 的创建与C# 5 的原生协程 (async/await)。这可以定义为“异步迭代器”。请参阅this thread 了解更多信息和my blog post 进行比较。
    • 嗯,好的,谢谢。我对 SO 很陌生,我不确定 wiki 礼仪。我会先阅读“规则”。
    • 不用担心 - 底线是,如果您可以改进某些东西,那么您应该这样做。
    • 对不起,那不是我。因此,如果人们继续还原它,我建议您添加另一个答案。
    • 别担心,很抱歉用 OOB cmets 打扰您。感谢您的提示。
    【解决方案3】:

    Here是一个使用线程实现协程的例子:

    所以我作弊。我使用线程,但我只 让其中一个一次运行。当我 创建一个协程,我创建一个线程, 然后做一些握手结束 调用 Monitor.Wait(),它 阻塞协程线程——它不会 继续运行,直到它被解锁。什么时候 是时候调用协程了, 我做了一个以 调用线程被阻塞,并且 协程线程可运行。同种 在回来的路上交接。

    这些交接有点贵, 与其他实现相比。 如果你需要速度,你会想要 编写你自己的状态机,并且 避免所有这些上下文切换。 (要么 你会想要使用光纤感知 运行时——切换光纤很漂亮 便宜。)但如果你想要表现力 代码,我认为协程有一些 承诺。

    【讨论】:

    • 使用线程的一个限制是(我相信在 Winforms 和 WPF 中)由线程 Larry 创建的表单的控件不能被线程 Moe 访问,即使线程Larry 没有对表单做任何事情,并且将被阻止,直到 Moe 也没有对它做任何事情。如果两个协程可以在同一个 OS 线程上运行,它们可以共享资源,否则只能由创建线程使用。
    【解决方案4】:

    引导缺失的部分

    管道是 golang 中相对于通道的缺失部分。频道实际上是让 golang 打勾的原因。通道是核心并发工具。如果您在 C# 中使用协程之类的东西,但使用线程同步原语(信号量、监视器、互锁等),那么情况就不一样了。

    几乎相同 - 管道,但已融入

    8 年后,.Net Standard (.Net Framework / .Net Core) 支持 Pipelines [https://blogs.msdn.microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-performance-io-in-net/]。管道是网络处理的首选。 Aspcore 现在排名前 11 的明文吞吐量请求率 [https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext]

    Microsoft 建议与网络流量交互的最佳实践:等待的网络字节(完成端口 IO)应将数据放入管道,而另一个线程应从管道异步读取数据。许多管道可以串联用于字节流上的各种处理。 Pipeline 有一个读取器和一个写入器游标,虚拟缓冲区大小会导致写入器背压,以减少不必要的缓冲内存使用,通常会减慢网络流量。

    Pipelines 和 Go Channels 之间存在一些关键差异。管道与 golang 通道不同。管道是关于传递可变字节而不是用于使用内存引用(包括指针)发出信号的 golang 通道。最后,没有与管道等效的select

    (管道使用 Spans [https://adamsitnik.com/Span/],它已经存在了一段时间,但现在在 .Net Core 中进行了深度优化。Span 显着提高了性能。.Net 核心支持进一步提高了性能,但只是渐进式的,所以 . Net Framework 使用完全没问题。)

    所以管道是一个内置标准,应该有助于替换 .Net 中的 golang 通道,但它们并不相同,而且在很多情况下管道不是解决方案。

    Golang Channel 的直接实现

    您需要小心(与 golang 一样)通过 .Net 通道传递的消息表明对象所有权的更改。这只有程序员可以跟踪和检查,如果你弄错了,你将有两个或更多线程访问数据而不同步。

    【讨论】:

    【解决方案5】:

    您可能对this 是一个隐藏协程使用的库感兴趣。例如读取文件:

    //Prepare the file stream
    FileStream sourceStream = File.Open("myFile.bin", FileMode.OpenOrCreate);
    sourceStream.Seek(0, SeekOrigin.End);
    
    //Invoke the task
    yield return InvokeTaskAndWait(sourceStream.WriteAsync(result, 0, result.Length));
    
    //Close the stream
    sourceStream.Close();
    

    这个库使用一个线程来运行所有协程,并允许调用任务以实现真正的异步操作。例如调用另一个方法作为协程(也就是为它的返回而让步

    //Given the signature
    //IEnumerable<string> ReadText(string path);
    
    var result = new Container();
    yield return InvokeLocalAndWait(() => _globalPathProvider.ReadText(path), container);
    var data = container.RawData as string;
    

    【讨论】:

      【解决方案6】:

      现在是 2020 年,C# 中发生了很多事情。我已经发表了一篇关于这个主题的文章,Asynchronous coroutines with C# 8.0 and IAsyncEnumerable

      在 C# 世界中,它们(协程)已被 Unity game development platform 和 Unity 普及 用途 IEnumerator-风格 方法和yield return

      在 C# 8 之前,无法在同一方法中组合 awaityield return,因此难以使用异步 在协程内部。现在,在编译器的支持下 IAsyncEnumerable,自然可以。

      【讨论】:

        猜你喜欢
        • 2011-04-04
        • 2017-09-16
        • 2018-08-10
        • 1970-01-01
        • 2020-01-22
        • 2017-05-15
        • 1970-01-01
        • 2016-04-03
        • 1970-01-01
        相关资源
        最近更新 更多