57

I was looking at this question, looking for a way to create a single-threaded, event-based nonblocking asynchronous web server in .NET.

This answer looked promising at first, by claiming that the body of the code runs in a single thread.

However, I tested this in 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);
    }
}

And the result was:

1
5

So it seems like, contrary to the answer, the thread initiating the read and the thread ending the read are not the same.

So now my question is, how do you to achieve a single-threaded, event-based nonblocking asynchronous web server in .NET?

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • my guess would be ... lots and lots of interop – Sam Saffron Jan 18 '12 at 06:11
  • If you are on mono you could use oars: http://code.google.com/p/oars/ – Sam Saffron Jan 18 '12 at 06:20
  • 2
    As per the docs, the base SynchronizationContext is actually free-threaded with no synchronization: http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.aspx. Post just calls ThreadPool.QueueUserWorkItem(). Subclasses like the WinForms one do synchronize to a single thread. – jeffora Jan 18 '12 at 06:27
  • 9
    Why is single threaded a key objective here? – Marc Gravell Jan 18 '12 at 07:30
  • @MarcGravell: Same reason why Node.js is single-threaded... threading is hard (and the person I'm going to be writing the app for seems to really like Node.js/Twisted/etc. because they're single-threaded, so I'm having a harder time convincing him of other not-so-single-threaded alternatives...) – user541686 Jan 18 '12 at 07:47
  • 5
    I had this exact same question! I researched NodeJS a bit, and ended up attempting to implement my own "version" of NodeJS in C#. While my experiment was crude, I wanted to see if it was possible to improve throughput/performance of IIS by porting the event-driven "NodeJS model" to .NET. Disappointed and feeling a bit silly, I realized IIS is already doing a pretty darn good job. In some ways, IIS works very similar to nodeJS. (Thread listens for requests and passes them to worker threads) I wrote about it here: http://jhottengineering.blogspot.com/2011/11/nodejs-simple-c-http-server.html – KTF Jan 18 '12 at 14:55
  • @KTF: The problem with worker threads is that they saturate after a certain point. But then again, I have no idea -- maybe Node saturates as well (since I believe it needs to perform I/O on separate threads, even though they do callbacks back on the main thread...). – user541686 Jan 20 '12 at 18:08
  • 5
    @Mehrdad, Here are the posts (`Node.js is Cancer`) that started the flamewar. I would recommend to read them before starting to implement a single-threaded server http://teddziuba.com/2011/10/node-js-is-cancer.html and http://teddziuba.com/2011/10/straight-talk-on-event-loops.html – L.B Jan 20 '12 at 21:05
  • 1
    Also see: https://docs.google.com/viewer?a=v&q=cache:W9_5P9_-_FgJ:www.mailinator.com/tymaPaulMultithreaded.pdf+java+10000+clients+blocking&hl=en&gl=de&pid=bl&srcid=ADGEESji9fNd5gye3tZ5LEYyv5OD86uPFsRk5i4Kez2braJqhzQdtWJFi59DZPlbjFP7c1Z20enK15Kvk01wQM73f_qr8KL6G-ex_3ekHyHFU_xcQyA2q3aAnOvlKwWA9pfDd4b6nxCT&sig=AHIEtbQVh-bulE-TtrqULt37C6fH06gbmA&pli=1 – Sam Saffron Jan 20 '12 at 22:46
  • There is a project on GitHub for doing just this. https://github.com/Rduerden/Node.cs – c17r Jan 18 '12 at 12:37
  • 1
    @AkashKava You don't need threads to scale, you need processes to scale. Single threaded and asynchronous works. And node.js is written by C developers who wanted a generic solution to an efficient web server without having to write it all in C. – Raynos Jan 26 '12 at 20:02

13 Answers13

40

The whole SetSynchronizationContext is a red herring, this is just a mechanism for marshalling, the work still happens in the IO Thread Pool.

What you are asking for is a way to queue and harvest Asynchronous Procedure Calls for all your IO work from the main thread. Many higher level frameworks wrap this kind functionality, the most famous one being libevent.

There is a great recap on the various options here: Whats the difference between epoll, poll, threadpool?.

.NET already takes care of scaling for you by have a special "IO Thread Pool" that handles IO access when you call the BeginXYZ methods. This IO Thread Pool must have at least 1 thread per processor on the box. see: ThreadPool.SetMaxThreads.

If single threaded app is a critical requirement (for some crazy reason) you could, of course, interop all of this stuff in using DllImport (see an example here)

However it would be a very complex and risky task:

Why don't we support APCs as a completion mechanism? APCs are really not a good general-purpose completion mechanism for user code. Managing the reentrancy introduced by APCs is nearly impossible; any time you block on a lock, for example, some arbitrary I/O completion might take over your thread. It might try to acquire locks of its own, which may introduce lock ordering problems and thus deadlock. Preventing this requires meticulous design, and the ability to make sure that someone else's code will never run during your alertable wait, and vice-versa. This greatly limits the usefulness of APCs.

So, to recap. If you want a single threaded managed process that does all its work using APC and completion ports, you are going to have to hand code it. Building it would be risky and tricky.

If you simply want high scale networking, you can keep using BeginXYZ and family and rest assured that it will perform well, since it uses APC. You pay a minor price marshalling stuff between threads and the .NET particular implementation.

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

The next step in scaling up the server is to use asynchronous I/O. Asynchronous I/O alleviates the need to create and manage threads. This leads to much simpler code and also is a more efficient I/O model. Asynchronous I/O utilizes callbacks to handle incoming data and connections, which means there are no lists to set up and scan and there is no need to create new worker threads to deal with the pending I/O.

An interesting, side fact, is that single threaded is not the fastest way to do async sockets on Windows using completion ports see: http://doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/info/comport.shtml

The goal of a server is to incur as few context switches as possible by having its threads avoid unnecessary blocking, while at the same time maximizing parallelism by using multiple threads. The ideal is for there to be a thread actively servicing a client request on every processor and for those threads not to block if there are additional requests waiting when they complete a request. For this to work correctly however, there must be a way for the application to activate another thread when one processing a client request blocks on I/O (like when it reads from a file as part of the processing).

Community
  • 1
  • 1
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • One of the troubles with the thread pool is that it saturates -- if you get the max number of I/O requests for example, it can't do any more I/O until something frees. From what I understand of Node.js (though please correct me if I'm wrong), there's no such limit, so I'm guessing it doesn't use APCs for your callbacks either (since those would stack overflow pretty easily due to reentrancy)... and yet Node.js does it. Any idea how? – user541686 Jan 18 '12 at 07:50
  • 2
    @Mehrdad not really, first you can grow it if it can not keep up, secondly internally the IO ThreadPool is using WaitForMultipleObjectsEx http://msdn.microsoft.com/en-us/library/ms687028.aspx ... any saturation would probably be marshalling, would be curious to see any benchmarks comparing a C++ implementation and C#. There are multiple points of marshalling in C# which will make it a bit slower. – Sam Saffron Jan 18 '12 at 07:54
  • @Mehrdad I updated this with a bunch of other links, I think your requirement is misguided – Sam Saffron Jan 18 '12 at 11:14
17

What you need is a "message loop" which takes the next task on a queue and executes it. Additionally, every task needs to be coded so that it completes as much work as possible without blocking, and then enqueues additional tasks to pick up a task that needs time later. There is nothing magical about this: never using a blocking call and never spawn additional threads.

For example, when processing an HTTP GET, the server can read as much data as is currently available on the socket. If this is not enough data to handle the request, then enqueue a new task to read from the socket again in the future. In the case of a FileStream, you want to set the ReadTimeout on the instance to a low value and be prepared to read fewer bytes than the entire file.

C# 5 actually makes this pattern much more trivial. Many people think that the async functionality implies multithreading, but that is not the case. Using async, you can essentially get the task queue I mentioned earlier without ever explicility managing it.

Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
Chris Pitman
  • 12,990
  • 3
  • 41
  • 56
  • To whoever downvoted, is there somethin about this answer which is deficient? – Chris Pitman Jan 18 '12 at 07:14
  • @SamSaffron What does async not do that the poster is asking for? – Chris Pitman Jan 18 '12 at 07:27
  • 2
    he asked for a "single threaded" solution, you can not tell the IO threadpool to have less threads than number of cores. You can not run the actual APC calls on the main thread. – Sam Saffron Jan 18 '12 at 07:29
  • 1
    @SamSaffron Nothing about async requires the thread pool to even be used, as the last link I posted touches on and this entry by Eric Lippert more directly focuses on: http://blogs.msdn.com/b/ericlippert/archive/2010/11/04/asynchrony-in-c-5-0-part-four-it-s-not-magic.aspx – Chris Pitman Jan 18 '12 at 07:35
  • I undid my downvote, cause I did answer and it is kind of poor manners. However, the **only** way to run IO via APC (which is the fastest way of doing it) is by using the managed IO threadpool. Somehow the work is going to reach there. See also: http://msdn.microsoft.com/en-us/library/aa365468.aspx – Sam Saffron Jan 18 '12 at 07:40
  • Reforcing "What you need is a "message loop""... I participated in a logging project to capture requests from our clients' webservices. These requisitions could come in thousands per second. In the logging part, all requests are asynchronous to a message queue (azure's Message Bus) and from there to the mongoDB. There is non blocking in the application (webservices) to perform the logging. – Diogo Rodrigues Jul 26 '17 at 17:02
10

Yes, it's called Manos de mono

Seriously, the entire idea behind manos is a single threaded asynchronous event driven web server.

High performance and scalable. Modeled after tornadoweb, the technology that powers friend feed, Manos is capable of thousands of simultaneous connections, ideal for applications that create persistent connections with the server.

The project appears to be low on maintenance and probably wouldn't be production ready but it makes a good case study as a demonstration that this is possible.

Raynos
  • 166,823
  • 56
  • 351
  • 396
7

Here's a great article series explaining what IO Completion Ports are and how they can be accessed via C# (i.e. you need to PInvoke into Win32 API calls from the Kernel32.dll).

Note: The libuv the cross platform IO framework behind node.js uses IOCP on Windows and libev on unix operating systems.

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

mythz
  • 141,670
  • 29
  • 246
  • 390
2

i am wondering nobody mentioned kayak it's basicly C#s answer to Pythons twisted, JavaScripts node.js or Rubys eventmachine

martyglaubitz
  • 992
  • 10
  • 21
1

I've been fiddling with my own simple implementation of such an architecture and I've put it up on github. I'm doing it more as a learning thing. But it's been a lot of fun and I think I'll flush it out more.

It's very alpha, so it's liable to change, but the code looks a little like this:

   //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");

   });

More information about it can be found here.

Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
1

I developed a server based on HttpListener and an event loop, supporting MVC, WebApi and routing. For what i have seen the performances are far better than standard IIS+MVC, for the MVCMusicStore i moved from 100 requests per seconds and 100% CPU to 350 with 30% CPU. If anybody would give it a try i am struggling for feedbacks! Actually is present a template to create websites based on this structure.

Note that I DON'T USE ASYNC/AWAIT until absolutely necessary. The only tasks i use there are the ones for the I/O bound operations like writing on the socket or reading files.

PS any suggestion or correction is welcome!

Kendar
  • 692
  • 7
  • 25
0

LibuvSharp is a wrapper for libuv, which is used in the node.js project for async IO. BUt it only contains only low level TCP/UDP/Pipe/Timer functionality. And it will stay like that, writing a webserver on top of it is an entire different story. It doesn't even support dns resolving, since this is just a protocol on top of udp.

Andrius Bentkus
  • 1,392
  • 11
  • 25
0

I believe it's possible, here is an open-source example written in VB.NET and C#:

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

It uses Event-based Asynchronous Pattern (EAP), IAsyncResult Pattern and thread pool (IOCP). It will serialize/marshal the messages (messages can be any native object such as a class instance) into binary packets, transfer the packets over TCP, and then deserialize/unmarshal the packets at the receiving end so you get your native object to work with. This part is somewhat like Protobuf or RPC.

It was originally developed as a "netcode" for real-time multiplayer gaming, but it can serve many purposes. Unfortunately I never got around to using it. Maybe someone else will.

The source code has a lot of comments so it should be easy to follow. Enjoy!

perry
  • 266
  • 1
  • 6
0

Here is one more implementation of the event-loop web server called SingleSand. It executes all custom logic inside single-threaded event loop but the web server is hosted in asp.net. Answering the question, it is generally not possible to run a pure single threaded app because of .NET multi-threaded nature. There are some activities that run in separate threads and developer cannot change their behavior.

Community
  • 1
  • 1
neleus
  • 2,230
  • 21
  • 36
0

you can this framework SignalR and this Blog about it

iomar
  • 465
  • 4
  • 4
0

Some kind of the support from operating system is essential here. For example, Mono uses epoll on Linux with asynchronous I/O, so it should scale really well (still thread pool). If you are looking and performance and scalability, definitely try it.

On the other hand, the example of C# (with native libs) webserver which is based around idea you have mentioned can be Manos de Mono. Project has not been active lately; however, idea and code is generally available. Read this (especially the "A closer look at Manos" part).

Edit:

If you just want to have callback fired on your main thread, you can do a little abuse of existing synchronization contexts like the WPF dispatcher. Your code, translated to this approach:

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);
        }
    }
}

prints what you wish. Plus you can set priorities with dispatcher. But agree, this is ugly, hacky and I do not know why I would do it that way for another reason than answer your demo request ;)

konrad.kruczynski
  • 46,413
  • 6
  • 36
  • 47
0

First about SynchronizationContext. It's just like Sam wrote. Base class won't give You single-thread functionality. You probably got that idea from WindowsFormsSynchronizationContext which provides functionality to execute code on UI thread.

You can read more here

I've written a piece of code that works with ThreadPool parameters. (Again something Sam already pointed out).

This code registers 3 asynchronous actions to be executed on free thread. They run in parallel until one of them changes ThreadPool parameters. Then each action is executed on the same thread.

It only proves that you can force .net app to use one thread. Real implementation of web server that would receive and process calls on only one thread is something entirely different :).

Here's the code:

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();
            }
        }
    }
}
Grzegorz W
  • 3,487
  • 1
  • 21
  • 21