48

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

是否有任何“正确”的方式在 C# 中实现协程?

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

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

4

6 回答 6

13

我相信使用新的 .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

于 2012-04-08T15:53:55.910 回答
13

编辑:您现在可以使用这些:.net 中是否有光纤 api?

我相信你应该看看Reactive Extensions for .NET。例如,可以使用迭代器和 yield语句来模拟协程。

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

于 2010-04-08T11:28:48.053 回答
6

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

所以我作弊。我使用线程,但我一次只让其中一个运行。当我创建协程时,我会创建一个线程,然后进行一些握手,最后调用 Monitor.Wait(),这会阻塞协程线程——它不会再运行,直到它被解除阻塞。当需要调用协程时,我会进行切换,结束时调用线程被阻塞,协程线程可运行。在回来的路上同样的交接。

与其他实现相比,这些切换有点昂贵。如果您需要速度,您将需要编写自己的状态机,并避免所有这些上下文切换。(或者你会想要使用一个可感知纤程的运行时——切换纤程非常便宜。)但是如果你想要富有表现力的代码,我认为协程有一些希望。

于 2010-04-08T11:34:59.640 回答
3

引导缺失的部分

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

几乎相同 - 管道,但经过烘焙

8 年后,.Net Standard (.Net Framework / .Net Core) 支持 Pipelines [ https://blogs.msdn.microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-性能-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 通道传递的消息表明对象的所有权发生了变化。这是只有程序员才能跟踪和检查的东西,如果你弄错了,你将有两个或更多线程访问数据而没有同步。

于 2018-09-12T00:27:28.333 回答
2

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

在 C# 世界中,它们(协程)已经被Unity 游戏开发平台推广,Unity 使用 IEnumerator-style 方法和yield return为此。

在 C# 8 之前,不可能在同一个方法中组合awaityield return,这使得在协程中使用异步变得困难。现在,有了编译器对 的支持 IAsyncEnumerable,自然就可以完成了。

于 2020-09-10T10:57:27.897 回答
1

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

//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;
于 2014-04-14T08:06:49.457 回答