Это неблокирующий, однопоточный асинхронный веб-сервер (как узел.js) возможно in.NET?
Я смотрел на этот вопрос, ища способ создать однопоточный, основанный на событиях неблокирующий асинхронный веб-сервер в. 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 ответов:
весь
SetSynchronizationContextэто отвлекающий маневр, это просто механизм для маршалинга, работа все еще происходит в пуле потоков ввода-вывода.то, что вы просите, - это способ встать в очередь и собрать урожай Асинхронные Вызовы Процедур для всей вашей работы IO из основного потока. Многие фреймворки более высокого уровня обертывают эту функциональность, самая известная из которых libevent.
здесь есть отличный обзор различных вариантов:что разница между epoll, poll, threadpool?.
.NET уже заботится о масштабировании для вас, имея специальный "пул потоков IO", который обрабатывает доступ к IO при вызове
BeginXYZметоды. Этот пул потоков ввода-вывода должен иметь по крайней мере 1 поток на процессор в поле. смотрите:ThreadPool.SetMaxThreads.Если однопоточное приложение является критическим требованием (по какой-то сумасшедшей причине) , вы можете, конечно, взаимодействовать со всеми этими вещами при использовании DllImport (см. пример)
однако это было бы очень сложная и рискованная задача:
почему мы не поддерживаем АСУ ТП в качестве механизма завершения? АСУ ТП на самом деле не является хорошим механизмом завершения общего назначения для пользовательского кода. Управление повторным доступом, введенным АСУ ТП, практически невозможно; например, каждый раз, когда вы блокируете блокировку, некоторое произвольное завершение ввода-вывода может взять на себя ваш поток. Он может попробовать чтобы получить собственные блокировки, которые могут привести к проблемам с упорядочением блокировок и, следовательно, к тупику. Предотвращение этого требует тщательного проектирования и возможности убедиться, что чужой код никогда не будет выполняться во время вашего ожидания оповещения, и наоборот. Это значительно ограничивает полезность АСУ ТП.
Итак, подведем итог. Если вы хотите однопоточным управляемый процесс, который выполняет всю свою работу с использованием APC и портов завершения, вам придется передать его код. Строить его было бы рискованно и сложно.
Если вы просто хотите большая сетей, вы можете продолжать использовать
BeginXYZи семья и будьте уверены, что он будет работать хорошо, так как он использует APC. Вы платите незначительную цену, сортируя материал между потоками и конкретной реализацией .NET.от:http://msdn.microsoft.com/en-us/magazine/cc300760.aspx
следующим шагом в масштабировании сервера является использование асинхронный ввод-вывод устраняет необходимость в создании потоков и управлении ими. Это приводит к гораздо более простому коду, а также является более эффективной моделью ввода-вывода. Асинхронный ввод-вывод использует обратные вызовы для обработки входящих данных и соединений, что означает, что нет списков для настройки и сканирования, и нет необходимости создавать новые рабочие потоки для обработки ожидающих ввода-вывода
интересный, побочный факт, что однопоточный не самый быстрый способ сделать асинхронные сокеты на Windows использование портов завершения см.:http://doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/info/comport.shtml
цель сервера состоит в том, чтобы выполнить как можно меньше переключений контекста, чтобы его потоки избегали ненужной блокировки, в то же время максимизируя параллелизм с помощью нескольких потоков. Идеально, чтобы на каждом процессоре был поток, активно обслуживающий запрос клиента, и чтобы эти потоки не блокировались, если есть дополнительные запросы ждут, когда они завершат запрос. Чтобы это работало правильно, однако, должен быть способ для приложения активировать другой поток, когда одна обработка запроса клиента блокирует ввод-вывод (например, когда он читает из файла как часть обработки).
вам нужен "цикл сообщений", который принимает следующую задачу в очереди и выполняет ее. Кроме того, каждая задача должна быть закодирована так, чтобы она выполняла как можно больше работы без блокировки, а затем помещала дополнительные задачи в очередь, чтобы забрать задачу, которая требует времени позже. В этом нет ничего волшебного: никогда не используйте блокирующий вызов и никогда не создавайте дополнительные потоки.
например, при обработке HTTP GET сервер может прочитать столько данных, сколько в настоящее время доступный на гнезде. Если этого недостаточно для обработки запроса, поставьте в очередь новую задачу для чтения из сокета снова в будущем. В случае FileStream, вы хотите установить ReadTimeout на экземпляре к низкому значению и быть готовым прочитать меньше байтов, чем весь файл.
C# 5 на самом деле делает этот шаблон гораздо более тривиальным. Многие люди думают, что async функциональность подразумевает многопоточность, но это не так. Используя асинхронность, вы можете по существу получить очередь задач, о которой я упоминал ранее, без какого-либо явного управления ею.
Да, это называется Manos de mono
серьезно, вся идея manos - это однопоточный асинхронный веб-сервер, управляемый событиями.
высокая производительность и масштабируемость. По образцу tornadoweb, технологии, которая обеспечивает питание друга, Manos способен к тысячам одновременных подключений, идеально подходит для приложений, которые создают постоянные соединения с сервером.
проект, похоже, находится на низком уровне обслуживание и, вероятно, не будет готово к производству, но это делает хороший пример в качестве демонстрации того, что это возможно.
вот отличная серия статей, объясняющая, что такое порты завершения ввода-вывода и как к ним можно получить доступ через C# (т. е. вам нужно PInvoke в вызовы Win32 API из Kernel32.файл DLL.)
Примечание:libuv кросс-платформенная инфраструктура ввода-вывода за узлом.js использует IOCP на Windows и libev на операционных системах unix.
http://www.theukwebdesigncompany.com/articles/iocp-thread-pooling.php
Мне интересно, что никто не упомянул каяк это в основном ответ C#на питоны витая, JavaScripts узел.js или Rubys eventmachine
я возился с моей собственной простой реализацией такой архитектуры, и я поставил его на 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"); });более подробную информацию о нем можно найти здесь.
Я разработал сервер на основе HttpListener и цикла событий, поддерживающий MVC, WebApi и маршрутизацию. Для того, что я видел, производительность намного лучше, чем стандартная IIS+MVC, для MVCMusicStore я перешел от 100 запросов в секунду и 100% CPU до 350 с 30% CPU. Если кто-нибудь даст ему попробовать я борюсь за обратные связи! На самом деле присутствует шаблон для создания сайтов на основе этой структуры.
обратите внимание, что я не использую ASYNC/AWAIT до тех пор, пока это абсолютно необходимо. Этот только задачи, которые я использую, есть те, которые связаны с операциями ввода-вывода, такими как запись в сокет или чтение файлов.
PS любое предложение или коррекция приветствуются!
здесь важна какая-то поддержка со стороны операционной системы. Например, Mono использует epoll в Linux с асинхронным вводом-выводом, поэтому он должен масштабироваться Очень хорошо (все еще пул потоков). Если вы ищете и производительность и масштабируемость, обязательно попробуйте.
С другой стороны, примером веб-сервера C# (с собственными библиотеками), который основан на идее, которую вы упомянули, может быть Manos de Mono. Проект не был активен в последнее время, однако, идея и код, как правило, доступна. Читать этой (особенно часть "более пристальный взгляд на Манос").
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); } } }печатает то, что вы хотите. Кроме того, вы можете установить приоритеты с диспетчером. Но согласитесь, это некрасиво, хаки и я не знаю, почему я бы сделал это таким образом для другого причина, чем ответить на ваш демо-запрос ;)
сначала о 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(); } } } }
LibuvSharp - это оболочка для libuv, которая используется в узле.проект js для асинхронного ввода-вывода. Но он содержит только низкоуровневую функциональность TCP/UDP/Pipe/Timer. И это останется так, написание веб-сервера поверх него-это совсем другая история. Он даже не поддерживает разрешение dns, так как это просто протокол поверх udp.
Я считаю, что это возможно, вот пример с открытым исходным кодом, написанный в VB.NET и C#:
https://github.com/perrybutler/dotnetsockets/
Он использует асинхронный шаблон на основе событий (EAP), шаблон IAsyncResult и пул потоков (IOCP). Он будет сериализовать / маршалировать сообщения (сообщения могут быть любым собственным объектом, таким как экземпляр класса) в двоичные пакеты, передавать пакеты по TCP, а затем десериализовать / демаршировать пакеты на принимающей стороне таким образом, вы получаете свой собственный объект для работы. Эта часть чем-то напоминает Protobuf или RPC.
первоначально он был разработан как" сетевой код " для многопользовательских игр в режиме реального времени, но он может служить многим целям. К сожалению, я никогда не использовал его. Может кто-то еще.
исходный код имеет много комментариев, так что это должно быть легко следовать. Наслаждайтесь!
еще один реализация веб-сервера цикла событий под названием SingleSand. Он выполняет всю пользовательскую логику внутри однопоточного цикла событий, но веб-сервер размещается в asp.net. Отвечая на вопрос, как правило, невозможно запустить чистое однопоточное приложение из-за многопоточной природы .NET. Есть некоторые деятельности, которые выполняются в отдельных потоках, и разработчик не может изменить свое поведение.
Comments