协同设计模式替代协同程序

时间:2009-08-23 19:24:26

标签: c# design-patterns mono continuations coroutine

目前,我有大量的C#计算(方法调用)驻留在将按顺序运行的队列中。每次计算都会使用一些高延迟服务(网络,磁盘......)。

我打算使用Mono协程来允许计算队列中的下一次计算继续,而之前的计算正在等待高延迟服务返回。但是,我更喜欢不依赖Mono协同程序。

是否有一种可在纯C#中实现的设计模式,使我能够在等待高延迟服务返回时处理其他计算?

由于

更新

我需要执行大量(> 10000)的任务,每个任务都将使用一些高延迟服务。在Windows上,您无法创建那么多线程。

更新

基本上,我需要一个设计模式来模拟Stackless Python(http://www.stackless.com/)中的tasklet的优点(如下所示)

  1. 巨大的任务数量
  2. 如果任务阻止队列中的下一个任务执行
  3. 没有浪费的cpu周期
  4. 任务之间的最小开销切换

10 个答案:

答案 0 :(得分:9)

您可以使用IEnumerable模拟协作微线程。不幸的是,这不适用于阻止API,因此您需要查找可以轮询的API,或者具有可用于信令的回调的API。

考虑一种方法

IEnumerable Thread ()
{
    //do some stuff
    Foo ();

    //co-operatively yield
    yield null;

    //do some more stuff
    Bar ();

    //sleep 2 seconds
    yield new TimeSpan (2000);
}

C#编译器会将其解包到状态机中 - 但外观是合作微线程的外观。

模式非常简单。您实现了一个“调度程序”,它保存所有活动IEnumerator的列表。当它循环遍历列表时,它使用MoveNext()“运行”每个列表。如果MoveNext的值为false,则线程已结束,并且调度程序将其从列表中删除。如果是,则调度程序访问Current属性以确定线程的当前状态。如果它是TimeSpan,则线程希望休眠,并且调度程序将其移动到某个队列,当睡眠时间结束时,该队列可以刷回主列表。

您可以使用其他返回对象来实现其他信号机制。例如,定义某种WaitHandle。如果线程产生其中一个,则可以将其移动到等待队列,直到发出句柄信号。或者你可以通过产生一组等待句柄来支持WaitAll。你甚至可以实现优先事项。

我在大约150LOC中做了一个简单的调度程序实现,但我还没有完成对代码的博客。它适用于我们的PhyreSharp PhyreEngine包装器(不会公开),在我们的一个演示中,它可以很好地控制几百个字符。我们从Unity3D引擎借用了这个概念 - 他们有一些在线文档可以从用户的角度来解释它。

答案 1 :(得分:6)

答案 2 :(得分:5)

我建议使用Thread Pool使用从任务队列中提供的活动任务列表,以可管理的批次一次从队列中执行多个任务。

在这种情况下,您的主工作线程最初会将N个任务从队列中弹出到活动任务列表中,以便调度到线程池(最有可能使用QueueUserWorkItem),其中N代表一个可管理的金额,赢得了' t重载线程池,使用线程调度和同步成本使应用程序陷入困境,或者由于每个任务的I / O内存开销合计而占用可用内存。

每当任务向工作线程发出完成信号时,您可以将其从活动任务列表中删除,并从任务队列中添加下一个要执行的任务。

这将允许您从队列中拥有一组滚动的N个任务。您可以操纵N来影响性能特征,并找到在您的特定情况下最佳的特性。

由于您最终会受到硬件操作(磁盘I / O和网络I / O,CPU)的瓶颈,我认为更小更好。在磁盘I / O上工作的两个线程池任务很可能执行速度不会超过一个。

您还可以通过将活动任务列表的大小和内容限制为特定类型的特定任务类型来实现灵活性。例如,如果您在具有4个内核的计算机上运行,​​您可能会发现性能最高的配置是同时运行的四个CPU绑定任务以及一个磁盘绑定任务和一个网络任务。

如果您已将一项任务归类为磁盘IO任务,则可以选择等到它完成后再添加另一个磁盘IO任务,并且您可以选择同时安排CPU绑定或网络绑定任务

希望这是有道理的!

PS:你对任务的顺序有任何依赖吗?

答案 3 :(得分:2)

你一定要查看Concurrency and Coordination Runtime。他们的一个样本准确地描述了您正在谈论的内容:您呼叫长延迟服务,并且CCR在您等待时有效地允许其他任务运行。它可以处理大量的任务,因为它不需要为每个任务生成一个线程,但如果你要求它会使用你的所有核心。

答案 4 :(得分:1)

这不是传统的多线程处理吗?

查看Reactor here

等模式

答案 5 :(得分:1)

将其写为使用Async IO可能就足够了。

如果没有强大的设计结构,这可能会导致代码难以调试。

答案 6 :(得分:1)

你应该看看这个:

http://www.replicator.org/node/80

这应该完全符合你的要求。不过,这是一个黑客攻击。

答案 7 :(得分:0)

有关“反应”模式的更多信息(如另一张海报所述)与.NET中的实现有关;又名“Linq to Events”

http://themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html

-Oisin

答案 8 :(得分:0)

事实上,如果您使用一个线程执行任务,您将失去游戏。想想为什么Node.js可以支持大量的连接。使用少量线程与异步IO !!!异步和等待函数可以帮助解决这个问题。

foreach (var task in tasks)
{
    await SendAsync(task.value);
    ReadAsync(); 
}

SendAsync()和ReadAsync()是异步IO调用的伪造函数。

Task parallelism也是一个不错的选择。但我不确定哪一个更快。你可以测试它们 在你的情况下。

答案 9 :(得分:0)

是的,你当然可以。您只需要构建一个调度程序机制,该机制将回调您提供的lambda并进入队列。我在Unity中编写的所有代码都使用了这种方法,而且我从不使用协同程序。我将使用诸如WWW之类的协同程序的方法包装起来以摆脱它。从理论上讲,协程可以更快,因为开销更少。实际上,他们为语言引入了新的语法来完成一项相当简单的任务,而且你不能在协同例程中正确地跟踪堆栈跟踪,因为你只能看到 - > Next。然后,您必须实现在另一个线程上运行队列中的任务的能力。但是,最新的.net中存在并行功能,您实际上是在编写类似的功能。它真的不会有很多代码。

如果有人有兴趣,我会发送代码,不要在我身上。