Подтвердить что ты не робот

Является ли в .NET неблокирующим, однопоточным асинхронным веб-сервером (например, Node.js)?

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

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

Однако я тестировал это на С#:

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?

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

В этой статье мы расскажем о том, какие порты ввода-вывода IO и как к ним можно получить доступ через С# (т.е. вам нужно вызвать PInvoke в вызовы API Win32 из файла Kernel32.dll).

Примечание. libuv кросс-платформенная платформа ввода-вывода за node.js использует IOCP для Windows и libev в операционных системах unix.

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

Ответ 5

Мне интересно, что никто не упоминал kayak, это в основном ответ С# на Pythons twisted, JavaScripts node.js или Rubys eventmachine

Ответ 6

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

   });

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

Ответ 7

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

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

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

Ответ 8

вы можете использовать эту инфраструктуру SignalR и Blog об этом

Ответ 9

Здесь важна какая-то поддержка операционной системы. Например, Mono использует epoll для Linux с асинхронным вводом-выводом, поэтому он должен масштабироваться очень хорошо (все еще пул потоков). Если вы ищете, производительность и масштабируемость, обязательно попробуйте.

С другой стороны, пример веб-сервера С# (с родными libs), основанного на идее, о которой вы упоминали, может быть Manos de Mono. В последнее время проект неактивен; однако идея и код обычно доступны. Прочитайте this (особенно в разделе "Более близкий взгляд на Manos" ).

Edit:

Если вы просто хотите, чтобы обратный вызов запускался в основном потоке, вы можете немного злоупотреблять существующими контекстами синхронизации, такими как диспетчер 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);
        }
    }
}

печатает то, что вы пожелаете. Кроме того, вы можете установить приоритеты с диспетчером. Но согласитесь, это уродливо, хаки, и я не знаю, почему я буду делать это по другой причине, кроме ответа на ваш демонстрационный запрос;)

Ответ 10

Сначала о SynchronizationContext. Это точно так же, как писал Сэм. Базовый класс не даст вам однопоточных функций. Вероятно, вы получили эту идею из WindowsFormsSynchronizationContext, которая предоставляет функциональные возможности для выполнения кода в потоке пользовательского интерфейса.

Вы можете прочитать больше здесь

Я написал фрагмент кода, который работает с параметрами ThreadPool. (Опять то, что Сэм уже указывал).

Этот код регистрирует 3 асинхронных действия, которые должны выполняться в свободном потоке. Они работают параллельно, пока один из них не изменит параметры ThreadPool. Затем каждое действие выполняется в том же потоке.

Это только доказывает, что вы можете заставить приложение .net использовать один поток. Реальная реализация веб-сервера, который будет принимать и обрабатывать вызовы только по одному потоку, - это совсем другое:).

Здесь код:

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

Ответ 11

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

Ответ 12

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

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

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

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

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

Ответ 13

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