Jest nieblokującym, jednowątkowym, asynchronicznym serwerem WWW (jak Node.js) możliwe in.NET?

W związku z tym, że nie jest to możliwe, nie jest to możliwe, ponieważ nie jest to możliwe, ponieważ nie jest to możliwe, ponieważ nie jest to możliwe .]}

Ta odpowiedź wyglądała obiecująco na początku, twierdząc, że treść kodu działa w jednym wątku.

Jednak testowałem to w 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);
    }
}

I wynik był:

1
5

Wydaje się więc, że wbrew odpowiedzi, wątek inicjujący czytanie i wątki kończące odczyt to , a nie to samo.

Więc teraz moje pytanie brzmi, jak osiągnąć jednowątkowy , oparty na zdarzeniach nieblokujący asynchroniczny serwer WWW w. Net?

Author: Community, 2012-01-18

13 answers

Całość SetSynchronizationContext to czerwony śledź, to tylko mechanizm do rozrzucania, praca nadal dzieje się w puli wątków IO.

To, o co prosisz, to sposób na kolejkowanie i pobieranie asynchronicznych wywołań procedur dla całej twojej pracy IO z głównego wątku. Wiele frameworków wyższego poziomu zawiera tego rodzaju funkcje, z których najbardziej znanym jest libevent .

Tutaj jest świetne podsumowanie różnych opcji: Jaka jest różnica między epoll, ankieta, threadpool?.

. NET już dba o skalowanie za pomocą specjalnej "puli wątków IO", która obsługuje dostęp do IO podczas wywoływania metod BeginXYZ. Ta pula wątków IO musi mieć co najmniej 1 wątek na procesor na pudełku. zobacz: ThreadPool.SetMaxThreads .

Jeśli aplikacja z pojedynczym wątkiem jest krytycznym wymogiem (z jakiegoś szalonego powodu), możesz oczywiście przeplatać wszystkie te rzeczy za pomocą DllImport (zobacz przykład tutaj)

Jakkolwiek to byłoby to bardzo skomplikowane i ryzykowne zadanie :

Dlaczego nie wspieramy APCs jako mechanizmu zakończenia? APC naprawdę nie są dobrym mechanizmem uzupełniania ogólnego przeznaczenia dla kodu użytkownika. Zarządzanie reentrancją wprowadzoną przez APCs jest prawie niemożliwe; za każdym razem, gdy blokujesz blokadę, na przykład, dowolne zakończenie we / wy może przejąć twój wątek. Może próbować nabyć własne zamki, co może spowodować problemy z porządkowaniem zamków, a tym samym impas. Zapobieganie temu wymaga skrupulatnego projektowania i możliwości upewnienia się, że czyjś kod nigdy nie uruchomi się podczas oczekiwania na alertable i vice-versa. To znacznie ogranicza przydatność APC.

Podsumowując. Jeśli chcesz pojedynczy wątek zarządzany proces, który wykonuje całą swoją pracę za pomocą portów APC i completion, będziesz musiał ręcznie go zakodować. Budowa byłaby ryzykowna i trudna.

Jeśli po prostu chcesz wysokiej skali sieci, możesz nadal używać BeginXYZ i rodziny i mieć pewność, że będzie dobrze działać, ponieważ używa APC. Płacisz niewielką cenę, rozrzucając rzeczy między wątkami a konkretną implementacją. NET.

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

Kolejnym krokiem w skalowaniu serwera jest użycie asynchronicznych We/Wy. asynchroniczne We / Wy łagodzi potrzebę tworzenia i zarządzania wątkami. Prowadzi to do znacznie prostszego kodu, a także jest bardziej wydajnym modelem We / Wy. Asynchroniczne We/Wy wykorzystuje wywołania zwrotne do obsługi przychodzących danych i połączeń, co oznacza, że nie ma list do skonfigurowania i skanowania oraz nie ma potrzeby tworzenia nowych wątków roboczych do obsługi oczekujących We / Wy.]}

Ciekawostką jest fakt, że single threaded nie jest najszybszym sposobem wykonywania gniazd asynchronicznych w systemie Windows za pomocą portów zakończenia patrz: http://doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/info/comport.shtml

Celem serwera jest jak najmniej kontekstu przełącza się, jak to możliwe, poprzez unikanie niepotrzebnego blokowania wątków, jednocześnie maksymalizując równoległość za pomocą wielu wątków. Idealnym rozwiązaniem jest, aby wątek aktywnie obsługiwał żądanie klienta na każdym procesorze i aby te wątki nie blokowały się, jeśli istnieją dodatkowe żądania oczekujące po zakończeniu żądania. Aby to jednak działało poprawnie, musi istnieć sposób, aby aplikacja aktywowała inny wątek, gdy przetwarzanie żądania klienta blokuje We / Wy (np. gdy czyta się z pliku jako część przetwarzania).

 39
Author: Sam Saffron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:20

Potrzebna jest "pętla komunikatów", która wykonuje kolejne zadanie w kolejce. Dodatkowo każde zadanie musi być zakodowane tak, aby wykonało jak najwięcej pracy bez blokowania, a następnie zapytuje o dodatkowe zadania, aby odebrać zadanie, które potrzebuje czasu później. Nie ma w tym nic magicznego: nigdy nie używaj połączenia blokującego i nigdy nie Generuj dodatkowych wątków.

Na przykład, podczas przetwarzania HTTP GET, serwer może odczytać tyle danych, ile jest obecnie dostępne na Gniazdo. Jeśli nie jest to wystarczająca ilość danych do obsługi żądania, to w przyszłości należy wysłać nowe zadanie do ponownego odczytu z gniazda. W przypadku strumienia plików, chcesz ustawić ReadTimeout na instancji na niską wartość i być przygotowanym na odczyt mniejszej liczby bajtów niż cały plik.

C # 5 czyni ten wzór znacznie bardziej trywialnym. Wiele osób uważa, że asynchroniczna funkcja zakłada wielowątkowość, ale tak nie jest . Za pomocą async, można zasadniczo uzyskaj kolejkę zadań, o której wspomniałem wcześniej, bez konieczności zarządzania nią.

 17
Author: Chris Pitman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-18 07:37:18

Tak, nazywa się Manos de mono

Poważnie, cała idea Manos jest pojedynczym, asynchronicznym serwerem WWW sterowanym zdarzeniami.

Wysoka wydajność i skalowalność. Wzorowany na tornadoweb, technologii, która zasila friend feed, Manos jest zdolny do tysięcy jednoczesnych połączeń, idealny dla aplikacji, które tworzą trwałe połączenia z serwerem.

Projekt wydaje się być mało konserwacyjny i prawdopodobnie nie byłby produkcja gotowa, ale stanowi dobre studium przypadku jako dowód na to, że jest to możliwe.

 11
Author: Raynos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-26 19:53:24

Oto świetna seria artykułów wyjaśniających, czym są porty zakończenia IO i jak można do nich uzyskać dostęp przez C# (tzn. musisz przypinać do wywołań API Win32 z jądra 32.dll).

Uwaga: libuv cross platform IO Framework behind node.js używa IOCP w systemach operacyjnych Windows i libev w systemach operacyjnych unix.

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

 7
Author: mythz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-18 07:57:27

Zastanawiam się, czy nikt nie wspomniał o kayak to podstawowa odpowiedź C # S na Pythony twisted , JavaScripts node.js lub Rubys eventmachine

 2
Author: martyglaubitz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-21 12:58:12

Bawiłem się własną prostą implementacją takiej architektury i umieściłem ją na github . Robię to bardziej jako nauka. Ale to była świetna zabawa i myślę, że będę ją bardziej spłukiwać.

Jest bardzo alpha, więc może się zmienić, ale kod wygląda trochę tak:

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

   });

Więcej informacji na ten temat można znaleźć tutaj .

 1
Author: Ben Lesh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-21 16:58:35

Stworzyłem serwer oparty na HttpListener i pętli zdarzeń, obsługujący MVC, WebApi i routing. Z tego, co widziałem, występy są znacznie lepsze niż standardowe IIS + MVC, dla MVCMusicStore przeniosłem ze 100 żądań na sekundę i 100% CPU do 350 z 30% CPU. Jeśli ktoś spróbuje, walczę o informacje zwrotne! Faktycznie jest obecny szablon do tworzenia stron internetowych w oparciu o tę strukturę.

Zauważ, że nie używam asynchronicznych / oczekujących, dopóki nie jest to absolutnie konieczne. Jedyny zadania, których używam są te do operacji związanych z We/Wy, takich jak zapis na gnieździe lub odczyt plików.

PS wszelkie sugestie lub poprawki mile widziane!

 1
Author: Kendar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-17 12:39:32

You can this framework SignalR i ten Blog o nim

 0
Author: iomar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-18 06:30:14

Pewien rodzaj wsparcia z systemu operacyjnego jest tutaj niezbędny. Na przykład, Mono używa epoll na Linuksie z asynchronicznymi We/Wy, więc powinno być naprawdę dobrze skalowane (nadal Pula wątków). Jeśli szukasz i wydajność i skalowalność, zdecydowanie spróbuj.

Z drugiej strony, przykładem serwera C# (z natywnymi bibliotekami), który opiera się na idei, o której wspomniałeś, może być Manos de mono. Projekt nie był ostatnio aktywny, jednak idea i Kod są ogólnie dostępne. Czytaj [[4]} to (szczególnie część "a closer look at Manos").

Edit:

Jeśli chcesz mieć wywołanie zwrotne w głównym wątku, możesz zrobić małe nadużycie istniejących kontekstów synchronizacji, takich jak dyspozytor WPF. Twój kod, przetłumaczony na to podejście:

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);
        }
    }
}
Drukuje to, co chcesz. Dodatkowo możesz ustawić priorytety za pomocą dyspozytora. Ale Zgadzam się, to jest brzydkie, hacky i nie wiem, dlaczego miałbym to zrobić w ten sposób z innego powodu niż odpowiedzieć na demo request;)
 0
Author: konrad.kruczynski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-24 21:01:25

Najpierw o SynchronizationContext. Tak jak napisał Sam. Klasa bazowa nie daje funkcjonalności jednowątkowej. Prawdopodobnie masz ten pomysł z WindowsFormsSynchronizationContext, który zapewnia funkcjonalność do wykonywania kodu w wątku UI.

Możesz przeczytać więcej tutaj

Napisałem fragment kodu, który działa z parametrami ThreadPool. (Znowu coś Sam już wskazał).

Ten kod rejestruje 3 asynchroniczne akcje do wykonania na wolnej nić. Działają one równolegle, dopóki jeden z nich nie zmieni parametrów ThreadPool. Następnie każda akcja jest wykonywana w tym samym wątku.

To tylko dowodzi, że można zmusić aplikację. NET do korzystania z jednego wątku. Prawdziwa implementacja web serwera, który odbierałby i przetwarzał wywołania tylko na jednym wątku to coś zupełnie innego :).

Oto kod:

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();
            }
        }
    }
}
 0
Author: Grzegorz W,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-26 17:53:43

LibuvSharp jest opakowaniem dla libuv, który jest używany w węźle.projekt js dla async IO. Ale zawiera tylko niskopoziomową funkcjonalność TCP/UDP/Pipe/Timer. I tak pozostanie, pisanie serwera www na nim to zupełnie inna historia. Nie obsługuje nawet rozwiązywania dns, ponieważ jest to tylko protokół na szczycie udp.

 0
Author: Andrius Bentkus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-25 20:41:08

Uważam, że to możliwe, oto przykład open-source napisany w VB.NET i C#:

Https://github.com/perrybutler/dotnetsockets/

Używa wzorca asynchronicznego opartego na zdarzeniach (EAP), wzorca IAsyncResult i puli wątków (iocp). Będzie serializował/nadawał wiadomości (wiadomości mogą być dowolnym natywnym obiektem, takim jak instancja klasy) do pakietów binarnych, przesyłał pakiety przez TCP, a następnie deserializował / usuwał pakiety na końcu odbioru, aby uzyskać natywny sprzeciw do pracy. Ta część jest trochę jak Protobuf lub RPC.

Został pierwotnie opracowany jako "netcode" do gier wieloosobowych w czasie rzeczywistym, ale może służyć wielu celom. Niestety nigdy go nie używałem. Może ktoś inny to zrobi.

Kod źródłowy ma wiele komentarzy, więc powinien być łatwy do naśladowania. Smacznego!

 0
Author: perry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-12 23:04:08

Oto jeszcze jedna implementacja serwera www pętli zdarzeń o nazwie SingleSand . Wykonuje całą niestandardową logikę wewnątrz jednowątkowej pętli zdarzeń, ale serwer WWW jest hostowany w asp.net. Odpowiadając na pytanie, generalnie nie jest możliwe uruchomienie czystej aplikacji z pojedynczym gwintem ze względu na wielowątkowy charakter. NET. Istnieją niektóre działania , które działają w oddzielnych wątkach i deweloper nie może zmienić ich zachowania.

 0
Author: neleus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:44