在.NET中是否可以使用非阻塞的单线程asynchronousWeb服务器(如Node.js)?

我在看这个问题 ,寻找一种在.NET中创build一个基于事件的单线程非阻塞asynchronousWeb服务器的方法。

首先, 这个答案看起来很有希望,声称代码的主体在一个线程中运行。

不过,我用C#testing了这个:

using System; using System.IO; using System.Threading; class Program { static void Main() { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); var sc = new SynchronizationContext(); SynchronizationContext.SetSynchronizationContext(sc); { var path = Environment.ExpandEnvironmentVariables( @"%SystemRoot%\Notepad.exe"); var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024 * 4, true); var bytes = new byte[1024]; fs.BeginRead(bytes, 0, bytes.Length, ar => { sc.Post(dummy => { var res = fs.EndRead(ar); // Are we in the same thread? Console.WriteLine(Thread.CurrentThread.ManagedThreadId); }, null); }, null); } Thread.Sleep(100); } } 

结果是:

1

因此,与答案相反,启动读取的线程和结束读取的线程并不相同。

所以现在我的问题是,如何在.NET中实现一个基于事件的单线程非阻塞asynchronousWeb服务器?

整个SetSynchronizationContext是一个红色的鲱鱼,这只是一个编组机制,工作仍然发生在IO线程池。

你所要求的是一种排队和收集来自主线程的所有IO工作的asynchronous过程调用的方法。 许多更高级的框架包装这种function,最有名的是libevent 。

有一个很好的回顾在这里的各种select: epoll,民意调查,线程池有什么区别? 。

.NET已经通过在调用BeginXYZ方法时拥有一个处理IO访问的特殊“IO线程池”来帮助您进行BeginXYZ 。 此IO线程池必须在每个处理器上至less有一个线程。 请参阅: ThreadPool.SetMaxThreads 。

如果单线程应用程序是一个关键的要求(出于一些疯狂的原因),当然,你可以使用DllImport来交互所有这些东西(参见这里的一个例子 )

不过,这将是一项非常复杂而又危险的任务 :

为什么我们不支持装甲运兵车作为完成机制? 对于用户代码,APC并不是一个很好的通用完成机制。 pipe理APC引入的重入几乎是不可能的。 任何时候你阻塞一个锁,例如,一些任意的I / O完成可能会占用你的线程。 它可能试图获得自己的锁,这可能会导致locking顺序问题,从而导致死锁。 防止这种情况需要一丝不苟的devise,并且能够确保别人的代码永远不会在您的警告等待期间运行,反之亦然。 这极大地限制了APC的有用性。

所以,回顾一下。 如果您想要一个使用APC和完成端口完成所有工作的单线程托pipe进程,您将不得不手动编写代码。 构build它将是有风险和棘手的。

如果你只是想要高规模的networking,你可以继续使用BeginXYZ和家庭,并放心,它会performance良好,因为它使用APC。 你支付线程和.NET特定实现之间的一个小的价格marshalling东西。

来自: http : //msdn.microsoft.com/en-us/magazine/cc300760.aspx

扩展服务器的下一步是使用asynchronousI / O。 asynchronousI / O减轻了创build和pipe理线程的需要。 这导致更简单的代码,也是一个更有效的I / O模型。 asynchronousI / O使用callback来处理传入的数据和连接,这意味着没有设置和扫描的列表,并且不需要创build新的工作线程来处理待处理的I / O。

一个有趣的事实是,单线程不是使用完成端口在Windows上执行asynchronous套接字的最快方法,请参阅: http : //doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/info/comport。 SHTML

服务器的目标是通过使线程避免不必要的阻塞,同时通过使用multithreading最大化并行性,尽可能地避免上下文切换。 理想的情况是在每个处理器上都有一个主动服务于客户请求的线程,并且这些线程在完成请求时还有其他请求在等待。 但是,为了正确地工作,当处理客户端请求在I / O上阻塞时(例如从文件读取作为处理的一部分时),应用程序必须有办法激活另一个线程。

你需要的是一个“消息循环”,它接受队列上的下一个任务并执行它。 此外,每个任务都需要编码,以便尽可能完成尽可能多的工作而不会阻塞,然后排队执行额外的任务来完成需要时间的任务。 没有什么不可思议的:永远不要使用阻塞调用,也不会产生额外的线程。

例如,在处理HTTP GET时,服务器可以读取与套接字上当前可用的数据相同的数据。 如果这是没有足够的数据来处理请求,那么排队一个新的任务在将来再次从套接字读取。 在FileStream的情况下,您希望将实例上的ReadTimeout设置为较低的值,并准备读取比整个文件更less的字节。

C#5实际上使这种模式变得更加微不足道。 许多人认为asynchronousfunction意味着multithreading,但事实并非如此 。 使用asynchronous,你基本上可以得到我前面提到的任务队列,而不用pipe理它。

是的,这叫做Manos de mono

说真的,manos背后的思想是一个单线程asynchronous事件驱动的Web服务器。

高性能和可扩展性。 模仿tornadoweb技术,支持朋友的饲料,马诺斯是能够成千上万的同时连接,理想的应用程序,build立与服务器的持久连接。

这个项目在维护上看起来很低,可能不会生产,但是这是一个很好的案例研究,certificate这是可能的。

下面是一个很好的文章系列,解释IO完成端口是什么以及如何通过C#访问它们(例如,您需要从Kernel32.dll到PInvoke到Win32 API调用中)。

注意: libuv node.js后面的跨平台IO框架在Windows上使用IOCP,在Unix操作系统上使用libev。

http://www.theukwebdesigncompany.com/articles/iocp-thread-pooling.php

我想知道没有人提到皮艇这是基本C#的答案python扭曲 ,JavaScript的node.js或Rubys 事件机

我一直在摆弄我自己简单的实现这样一个架构,我把它放在github上 。 我正在做更多的学习的东西。 但是这很有趣,而且我想我会更加清楚。

这是非常的阿尔法,所以它可能会改变,但代码看起来有点像这样:

  //Start the event loop. EventLoop.Start(() => { //Create a Hello World server on port 1337. Server.Create((req, res) => { res.Write("<h1>Hello World</h1>"); }).Listen("http://*:1337"); }); 

有关它的更多信息可以在这里find。

我开发了一个基于HttpListener和事件循环的服务器,支持MVC,WebApi和路由。 对于我所看到的性能比标准的IIS + MVC要好得多,对于MVCMusicStore,我从每秒100个请求和100%的CPU移动到了30%的CPU。 如果有人愿意尝试,我正在努力反馈! 其实是提供一个模板来创build基于这个结构的网站。

请注意,我绝对不需要使用ASYNC / AWAIT。 我使用的唯一的任务是那些I / O绑定操作,如写在套接字上或读取文件。

PS任何build议或更正是值得欢迎的!

  • 文档
  • Node.Cs上的MvcMusicStore示例端口
  • 在Nuget上的软件包

你可以把这个框架的SignalR和这个Blog讲一下吧

操作系统的某种支持在这里是必不可less的。 例如,Mono在Linux上使用asynchronousI / O的epoll,所以它应该很好地扩展(仍然是线程池)。 如果你正在寻找和性能和可扩展性,一定要尝试。

另一方面,基于你提到的想法的C#(带有本地库)webserver的例子可以是Manos de Mono。 项目最近还没有活动, 然而,想法和代码通常是可用的。 阅读这个 (特别是“仔细看马诺斯”部分)。

编辑:

如果您只想在主线程上触发callback,则可以对WPF调度程序等现有同步上下文进行一些修改。 你的代码翻译成这种方法:

 using System; using System.IO; using System.Threading; using System.Windows; namespace Node { class Program { public static void Main() { var app = new Application(); app.Startup += ServerStart; app.Run(); } private static void ServerStart(object sender, StartupEventArgs e) { var dispatcher = ((Application) sender).Dispatcher; Console.WriteLine(Thread.CurrentThread.ManagedThreadId); var path = Environment.ExpandEnvironmentVariables( @"%SystemRoot%\Notepad.exe"); var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024 * 4, true); var bytes = new byte[1024]; fs.BeginRead(bytes, 0, bytes.Length, ar => { dispatcher.BeginInvoke(new Action(() => { var res = fs.EndRead(ar); // Are we in the same thread? Console.WriteLine(Thread.CurrentThread.ManagedThreadId); })); }, null); } } } 

打印你的愿望。 另外,您可以使用调度程序设置优先级。 但同意,这是丑陋的,哈克,我不知道为什么我会这样做,另一个原因,而不是回答你的演示请求;)

首先关于SynchronizationContext。 就像Sam写的一样。 基类不会给你单线程function。 你可能从WindowsFormsSynchronizationContext得到了这个想法,它提供了在UI线程上执行代码的function。

你可以在这里阅读更多

我写了一段与ThreadPool参数一起工作的代码。 (再次Sam已经指出)。

这个代码注册了3个在空闲线程上执行的asynchronous动作。 它们并行运行,直到其中一个更改了ThreadPool参数。 然后每个动作在同一个线程上执行。

它只能certificate你可以强制.net应用程序使用一个线程。 Web服务器的真正实现,只接收和处理一个线程上的调用是完全不同的:)。

代码如下:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.IO; namespace SingleThreadTest { class Program { class TestState { internal string ID { get; set; } internal int Count { get; set; } internal int ChangeCount { get; set; } } static ManualResetEvent s_event = new ManualResetEvent(false); static void Main(string[] args) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); int nWorkerThreads; int nCompletionPortThreads; ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads); Console.WriteLine(String.Format("Max Workers: {0} Ports: {1}",nWorkerThreads,nCompletionPortThreads)); ThreadPool.GetMinThreads(out nWorkerThreads, out nCompletionPortThreads); Console.WriteLine(String.Format("Min Workers: {0} Ports: {1}",nWorkerThreads,nCompletionPortThreads)); ThreadPool.QueueUserWorkItem(new WaitCallback(LetsRunLikeCrazy), new TestState() { ID = "A ", Count = 10, ChangeCount = 0 }); ThreadPool.QueueUserWorkItem(new WaitCallback(LetsRunLikeCrazy), new TestState() { ID = " B ", Count = 10, ChangeCount = 5 }); ThreadPool.QueueUserWorkItem(new WaitCallback(LetsRunLikeCrazy), new TestState() { ID = " C", Count = 10, ChangeCount = 0 }); s_event.WaitOne(); Console.WriteLine("Press enter..."); Console.In.ReadLine(); } static void LetsRunLikeCrazy(object o) { if (s_event.WaitOne(0)) { return; } TestState oState = o as TestState; if (oState != null) { // Are we in the same thread? Console.WriteLine(String.Format("Hello. Start id: {0} in thread: {1}",oState.ID, Thread.CurrentThread.ManagedThreadId)); Thread.Sleep(1000); oState.Count -= 1; if (oState.ChangeCount == oState.Count) { int nWorkerThreads = 1; int nCompletionPortThreads = 1; ThreadPool.SetMinThreads(nWorkerThreads, nCompletionPortThreads); ThreadPool.SetMaxThreads(nWorkerThreads, nCompletionPortThreads); ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads); Console.WriteLine(String.Format("New Max Workers: {0} Ports: {1}", nWorkerThreads, nCompletionPortThreads)); ThreadPool.GetMinThreads(out nWorkerThreads, out nCompletionPortThreads); Console.WriteLine(String.Format("New Min Workers: {0} Ports: {1}", nWorkerThreads, nCompletionPortThreads)); } if (oState.Count > 0) { Console.WriteLine(String.Format("Hello. End id: {0} in thread: {1}", oState.ID, Thread.CurrentThread.ManagedThreadId)); ThreadPool.QueueUserWorkItem(new WaitCallback(LetsRunLikeCrazy), oState); } else { Console.WriteLine(String.Format("Hello. End id: {0} in thread: {1}", oState.ID, Thread.CurrentThread.ManagedThreadId)); s_event.Set(); } } else { Console.WriteLine("Error !!!"); s_event.Set(); } } } } 

LibuvSharp是libuv的包装器,在asynchronousIO的node.js项目中使用。 它只包含低级别的TCP / UDP / Pipe / Timerfunction。 而且它会保持这样的状态,在上面写Web服务器是一个完全不同的故事。 它甚至不支持dnsparsing,因为这只是udp之上的协议。

我相信这是可能的,这是一个用VB.NET和C#编写的开源示例:

https://github.com/perrybutler/dotnetsockets/

它使用基于事件的asynchronous模式(EAP),IAsyncResult模式和线程池(IOCP)。 它会将消息序列化/封送(消息可以是任何本地对象,如类实例)为二进制数据包,通过TCP传输数据包,然后在接收端对数据包进行反序列化/解组,以便让本地对象与。 这部分有点像Protobuf或RPC。

它最初是作为实时多人游戏的“networking编码”开发的,但它可以用于很多目的。 不幸的是我从来没有使用它。 也许别人会。

源代码有很多评论,所以应该很容易遵循。 请享用!

以下是一个名为SingleSand的事件循环Web服务器的更多实现 。 它执行单线程事件循环内的所有自定义逻辑,但Web服务器托pipe在asp.net中。 回答这个问题,由于.NETmultithreading性质,通常不可能运行纯粹的单线程应用程序。 有一些活动在单独的线程中运行,开发人员无法更改其行为。