.NET中是否可以使用非阻塞,单线程,异步Web服务器(如Node.js)?

Meh*_*dad 57 c# events multithreading asynchronous node.js

我正在研究这个问题,寻找一种在.NET中创建基于事件的单线程非阻塞异步Web服务器的方法.

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

但是,我在C#中对此进行了测试:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

结果是:

1
5

因此,与答案相反,似乎启动读取的线程和结束读取的线程一样.

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

Sam*_*ron 40

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

您要求的是从主线程排队并获取所有IO工作的异步过程调用的方法.许多更高级别的框架包含这种功能,最着名的是libevent.

这里的各种选项有一个很好的回顾:epoll,民意调查,线程池之间的区别是什么?.

.NET已经通过在调用BeginXYZ方法时使用一个特殊的"IO线程池"来处理IO访问,从而为您进行扩展.此IO线程池必须在每个处理器上至少有1个线程.请参阅:ThreadPool.SetMaxThreads.

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

然而,这将是一项非常复杂和冒险的任务:

为什么我们不支持APC作为完成机制?对于用户代码,APC实际上不是一个很好的通用完成机制.管理APC引入的重入几乎是不可能的; 例如,任何时候阻塞锁定,一些任意的I/O完成都可能会占用你的线程.它可能会尝试获取自己的锁,这可能会引入锁定排序问题,从而导致死锁.防止这种情况需要精心设计,并且能够确保在您的警报等待期间其他人的代码永远不会运行,反之亦然.这极大地限制了APC的有用性.

所以,回顾一下.如果您希望使用单线程托管进程使用APC和完成端口完成所有工作,那么您将不得不手动编写代码.建立它将是冒险和棘手的.

如果你只是想要高规模的网络,你可以继续使用BeginXYZ和家人,并放心它会表现良好,因为它使用APC.你在线程和.NET特定实现之间支付一个小的价格编组.

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

扩展服务器的下一步是使用异步I/O. 异步I/O减少了创建和管理线程的需要.这导致更简单的代码,也是一种更高效的I/O模型.异步I/O利用回调来处理传入的数据和连接,这意味着没有要设置和扫描的列表,也没有必要创建新的工作线程来处理挂起的I/O.

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

服务器的目标是通过让线程避免不必要的阻塞来尽可能少地进行上下文切换,同时通过使用多个线程来最大化并行性.理想情况是,在每个处理器上有一个主动服务于客户端请求的线程,并且如果在完成请求时有其他请求等待,那么这些线程不会阻塞.然而,为了使其正常工作,当处理客户端请求在I/O上阻塞时(例如,当它作为处理的一部分从文件读取时),应用程序必须有一种方法来激活另一个线程.

  • @Mehrdad不是真的,首先你可以增长它,如果它无法跟上,其次内部IO ThreadPool使用WaitForMultipleObjectsEx http://msdn.microsoft.com/en-us/library/ms687028.aspx ...任何饱和会可能是编组,很想看到任何比较C++实现和C#的基准测试.C#中有多个编组点会让它慢一点. (2认同)

Chr*_*man 17

你需要的是一个"消息循环",它接受队列上的下一个任务并执行它.此外,每个任务都需要进行编码,以便在不阻塞的情况下完成尽可能多的工作,然后将其他任务排入队列以获取稍后需要时间的任务.这没有什么神奇之处:从不使用阻塞调用,也从不产生额外的线程.

例如,在处理HTTP GET时,服务器可以读取插槽上当前可用的数据.如果这不足以处理请求,则将新任务排入队列,以便将来再次从套接字读取.对于FileStream,您希望将实例上的ReadTimeout设置为较低的值,并准备读取比整个文件更少的字节.

C#5实际上使这种模式更加微不足道.许多人认为异步功能意味着多线程,但事实并非如此.使用async,您基本上可以获得我之前提到的任务队列,而无需明确管理它.

  • 他要求一个"单线程"解决方案,你不能告诉IO线程池的线程数少于核心数.您无法在主线程上运行实际的APC调用. (2认同)

Ray*_*nos 10

是的,它被称为Manos de mono

说真的,manos背后的整个想法是一个单线程异步事件驱动的Web服务器.

高性能和可扩展性.仿照tornadoweb(支持朋友馈送的技术),Manos能够实现数千个同时连接,非常适合与服务器建立持久连接的应用程序.

该项目似乎维护成本低,可能不会准备好生产,但它是一个很好的案例研究,证明这是可能的.


myt*_*thz 7

这是一篇很棒的文章系列,解释了IO完成端口是什么以及如何通过C#访问它们(即你需要从Kernel32.dll进入PInvoke到Win32 API调用).

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

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