Возможен ли в .NET неблокирующий однопоточный асинхронный веб-сервер (например, Node.js)? - PullRequest
57 голосов
/ 18 января 2012

Я смотрел на этот вопрос , искал способ создания однопоточного неблокирующего асинхронного веб-сервера на основе событий в .NET.

Этот ответ на первый взгляд выглядел многообещающим, утверждая, что тело кода выполняется в одном потоке.

Однако я проверял это на 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?

Ответы [ 13 ]

40 голосов
/ 18 января 2012

Вся SetSynchronizationContext - красная сельдь, это просто механизм сортировки, работа по-прежнему происходит в пуле потоков ввода-вывода.

То, что вы запрашиваете, - это способ ставить в очередь и собирать данные Асинхронные вызовы процедур для всей вашей работы ввода-вывода из основного потока.Многие высокоуровневые фреймворки обертывают такую ​​функциональность, самой известной из которых является libevent .

. Здесь есть большое резюме по различным опциям: В чем разница между epoll, poll,Threadpool .

.NET уже позаботился о масштабировании для вас благодаря специальному «пулу потоков ввода-вывода», который обрабатывает доступ ввода-вывода при вызове методов BeginXYZ.Этот пул потоков ввода-вывода должен иметь по крайней мере 1 поток на процессор на коробке.см .: ThreadPool.SetMaxThreads .

Если однопоточное приложение является критическим требованием (по какой-то безумной причине), вы, конечно, можете взаимодействовать со всем этим, используя DllImport (см. пример здесь )

Однако это будет очень сложное и рискованное задание :

Почему бы немы поддерживаем БТРы как механизм доработки?APC действительно не являются хорошим механизмом завершения общего назначения для пользовательского кода.Управление повторным входом, введенным БТР, практически невозможно;например, каждый раз, когда вы блокируете блокировку, произвольное завершение ввода-вывода может занять ваш поток.Он может попытаться получить собственные блокировки, что может привести к проблемам с порядком блокировки и, следовательно, к тупику.Предотвращение этого требует тщательного проектирования и способности гарантировать, что чужой код никогда не будет работать во время вашего ожидаемого ожидания, и наоборот.Это сильно ограничивает полезность БТРов.

Итак, подведем итог.Если вам нужен однопоточный управляемый процесс , который выполняет всю свою работу с использованием APC и портов завершения, вам придется вручную его кодировать.Строить это было бы рискованно и сложно.

Если вам просто нужна высокопроизводительная сеть, вы можете продолжать использовать BeginXYZ и семью, и будьте уверены, что он будет работать хорошо, поскольку использует APC.Вы платите небольшую цену за сортировку между потоками и конкретной реализацией .NET.

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

Следующим шагом в расширении сервера является использование асинхронного ввода-вывода.Асинхронный ввод-вывод избавляет от необходимости создавать и управлять потоками.Это приводит к гораздо более простому коду, а также к более эффективной модели ввода / вывода.Асинхронный ввод-вывод использует обратные вызовы для обработки входящих данных и соединений, что означает, что нет списков для настройки и сканирования, и нет необходимости создавать новые рабочие потоки для обработки ожидающих операций ввода-вывода.

Интересным, побочным фактом является то, что однопоточный не самый быстрый способ сделать асинхронные сокеты в Windows с использованием портов завершения, см .: http://doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/info/comport.shtml

Цель сервера состоит в том, чтобывыполнять как можно меньше переключений контекста, так как его потоки избегают ненужной блокировки, и в то же время максимизируют параллелизм за счет использования нескольких потоков.В идеале должен быть поток, активно обслуживающий клиентский запрос на каждом процессоре, и чтобы эти потоки не блокировались, если есть дополнительные запросы, ожидающие, когда они завершат запрос.Однако, чтобы это работало правильно, у приложения должен быть способ активировать другой поток, когда одна обработка клиентского запроса блокирует ввод-вывод (например, когда он читает из файла как часть обработки).

17 голосов
/ 18 января 2012

Вам нужен «цикл сообщений», который берет следующую задачу в очередь и выполняет ее.Кроме того, каждая задача должна быть закодирована таким образом, чтобы она выполняла как можно больше работы без блокирования, а затем ставила в очередь дополнительные задачи, чтобы подобрать задачу, которая требует времени спустя.В этом нет ничего волшебного: никогда не использовать блокирующий вызов и никогда не создавать дополнительные потоки.

Например, при обработке HTTP GET сервер может прочитать столько данных, сколько в данный момент доступно в сокете.Если данных недостаточно для обработки запроса, поставьте новую задачу в очередь для чтения из сокета в будущем.В случае FileStream вы хотите установить для параметра ReadTimeout для экземпляра низкое значение и быть готовым прочитать меньше байтов, чем весь файл.

C # 5 на самом деле делает этот шаблон гораздо более тривиальным.Многие думают, что асинхронная функциональность подразумевает многопоточность, но это не так .Используя async, вы можете получить очередь задач, о которой я упоминал ранее, без каких-либо явных объяснений.

10 голосов
/ 26 января 2012

Да, это называется Manos de mono

Серьезно, вся идея manos - это однопоточный асинхронный веб-сервер, управляемый событиями.

Высокая производительность и масштабируемость. Модель Manos, созданная по аналогии с технологией tornadoweb, поддерживающей подачу в друзья, позволяет одновременно использовать тысячи соединений, что идеально подходит для приложений, которые создают постоянные соединения с сервером.

Похоже, что в проекте мало технического обслуживания, и, вероятно, он не будет готов к производству, но это хороший пример для демонстрации того, что это возможно.

7 голосов
/ 18 января 2012

Вот отличная серия статей, объясняющих, что такое порты завершения ввода-вывода и как к ним можно обращаться через C # (т.е. вам нужно PInvoke в вызовы Win32 API из Kernel32.dll).* libuv кроссплатформенная инфраструктура ввода-вывода за node.js использует IOCP в Windows и libev в операционных системах Unix.

2 голосов
/ 21 сентября 2012

мне интересно, никто не упомянул каяк это в основном ответ C # на Питонов витой , JavaScripts node.js или Rubys eventmachine

1 голос
/ 17 апреля 2014

Я разработал сервер на основе HttpListener и цикла обработки событий, поддерживающего MVC, WebApi и маршрутизацию. Для того, что я видел, производительность намного лучше, чем стандартная IIS + MVC, для MVCMusicStore я перешел со 100 запросов в секунду и 100% CPU на 350 с 30% CPU. Если кто-нибудь попробует, я борюсь за отзывы! На самом деле присутствует шаблон для создания сайтов на основе этой структуры.

Обратите внимание, что я НЕ ИСПОЛЬЗУЮ ASYNC / AWAIT до тех пор, пока это не станет абсолютно необходимым. Единственными задачами, которые я здесь использую, являются задачи для операций ввода-вывода, таких как запись в сокет или чтение файлов.

PS любые предложения или исправления приветствуются!

1 голос
/ 21 июня 2012

Я возился с собственной простой реализацией такой архитектуры и поставил ее на 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");

   });

Более подробную информацию об этом можно найти здесь .

0 голосов
/ 25 октября 2014

Вот еще одна реализация веб-сервера цикла обработки событий, называемая SingleSand . Он выполняет всю пользовательскую логику в однопоточном цикле событий, но веб-сервер размещен на asp.net. Отвечая на вопрос, как правило, невозможно запустить чисто однопоточное приложение из-за многопоточной природы .NET. Есть некоторые действия, которые выполняются в отдельных потоках, и разработчик не может изменить их поведение.

0 голосов
/ 13 августа 2014

Я считаю, что это возможно, вот пример с открытым исходным кодом, написанный на VB.NET и C #:

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

Он использует асинхронный шаблон на основе событий (EAP), шаблон IAsyncResult и пул потоков (IOCP). Он будет сериализовать / маршалировать сообщения (сообщения могут быть любым собственным объектом, например экземпляром класса) в двоичные пакеты, передавать пакеты по TCP, а затем десериализовывать / демаршировать пакеты на принимающей стороне, чтобы вы могли работать с вашим собственным объектом , Эта часть чем-то похожа на Protobuf или RPC.

Первоначально он был разработан как «сетевой код» для многопользовательских игр в реальном времени, но он может служить многим целям. К сожалению, я никогда не удосужился использовать его. Может быть, кто-то еще будет.

В исходном коде много комментариев, поэтому за ним должно быть легко следить. Наслаждайтесь!

0 голосов
/ 26 декабря 2013

LibuvSharp - это оболочка для libuv, которая используется в проекте node.js для асинхронного ввода-вывода. Однако он содержит только низкоуровневые функции TCP / UDP / Pipe / Timer. И так будет и дальше, написание веб-сервера поверх него - это совсем другая история. Он даже не поддерживает разрешение DNS, поскольку это просто протокол поверх udp.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...