我正在研究this question,寻找一种在.NET中创建单线程、基于事件的非阻塞异步web服务器的方法。
This answer一开始看起来很有前途,因为它声称代码体运行在单个线程中。
但是,我在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);
}
}
结果是:
1
5
因此,与答案相反,启动读取的线程和结束读取的线程似乎并不相同。
所以现在我的问题是,如何在.NET中实现单线程、基于事件的非阻塞异步web服务器?
发布于 2012-01-18 15:21:57
整个SetSynchronizationContext
是在转移注意力,这只是一种编组机制,工作仍然发生在IO线程池中。
您所要求的是一种从主线程对所有IO工作进行排队和获取Asynchronous Procedure Calls的方法。许多更高级的框架都封装了这种功能,其中最著名的就是libevent。
这里有一个关于各种选项的很好的回顾:Whats the difference between epoll, poll, threadpool?。
当你调用BeginXYZ
方法时,.NET已经为你提供了一个特殊的"IO线程池“来处理IO访问。此IO线程池中的每个处理器必须至少有一个线程。请参阅:ThreadPool.SetMaxThreads
如果单线程应用程序是一个关键需求(出于某种疯狂的原因),您当然可以在使用DllImport (参见example here)中互操作所有这些东西
然而,这将是一个非常complex and risky task:
为什么我们不支持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时激活另一个线程(例如,当它在处理过程中读取文件时)。
发布于 2012-01-18 14:28:35
您需要的是一个“消息循环”,它获取队列中的下一个任务并执行它。此外,每个任务都需要进行编码,以便在不阻塞的情况下完成尽可能多的工作,然后将额外的任务排入队列,以便稍后获取需要时间的任务。这并没有什么神奇之处:永远不会使用阻塞调用,也不会产生额外的线程。
例如,在处理HTTP GET时,服务器可以读取套接字上当前可用的尽可能多的数据。如果这没有足够的数据来处理请求,则将一个新任务排入队列,以便将来再次从套接字读取。对于FileStream,您希望将实例上的ReadTimeout设置为较低的值,并准备读取比整个文件更少的字节。
C# 5实际上使这个模式变得简单得多。许多人认为async functionality意味着多线程,但是that is not the case。使用async,你基本上可以获得我之前提到的任务队列,而不需要显式地管理它。
发布于 2012-01-27 03:53:24
是的,它叫Manos de mono
说真的,manos背后的整个想法是一个单线程异步事件驱动的web服务器。
高性能和可扩展性。Manos仿照tornadoweb技术,支持好友馈送,能够同时进行数千个连接,非常适合与服务器创建持久连接的应用程序。
该项目的维护成本似乎很低,可能还没有准备好投入生产,但它提供了一个很好的案例研究,证明了这是可能的。
https://stackoverflow.com/questions/8905860
复制相似问题