web-dev-qa-db-ja.com

.NETで非ブロッキング、シングルスレッド、非同期のWebサーバー(Node.jsなど)は可能ですか?

私は この質問 を見ていました、シングルスレッド、イベントベースのノンブロッキング非同期Webサーバーを作成する方法を探していました.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でシングルスレッド、イベントベースのノンブロッキング非同期Webサーバーをどのように実現するのですか?

57
user541686

全体のSetSynchronizationContextは赤いニシンです。これはマーシャリングのメカニズムにすぎず、作業は引き続きIOスレッドプールで行われます。

あなたが求めているのは、すべてのIO作業をメインスレッドから処理するために、 非同期プロシージャコール をキューに入れて収集する方法です。レベルのフレームワークはこの種の機能をラップし、最も有名なものは libevent です。

epoll、poll、threadpoolの違いは何ですか?

.NETは、BeginXYZメソッドを呼び出すときにIOアクセスを処理する特別な「IOスレッドプール」を用意することで、すでにスケーリングを処理しています。これは、IOスレッドプールには、ボックス上のプロセッサごとに少なくとも1つのスレッドが必要です。 ThreadPool.SetMaxThreads を参照してください。

シングルスレッドアプリが重要な要件である場合は(クレイジーな理由により)、 DllImport を使用してこれらすべてを相互運用できます( ここに例

しかし、それは非常に 複雑で危険なタスクです

なぜAPCを補完メカニズムとしてサポートしないのですか? APCは、実際には、ユーザーコードの優れた汎用補完メカニズムではありません。 APCによって導入された再入可能性を管理することはほぼ不可能です。たとえば、ロックをブロックすると、任意のI/O完了がスレッドを引き継ぐ可能性があります。独自のロックを取得しようとする場合があり、ロックの順序付けの問題が発生し、デッドロックが発生する可能性があります。これを防ぐには、細心の注意を払った設計と、警告可能な待機中に他の誰かのコードが実行されないようにする機能、およびその逆が必要です。これにより、APCの有用性が大幅に制限されます。

要約すると、 APCと完了ポートを使用してすべての作業を行うシングルスレッド管理対象プロセスが必要な場合は、手動でコーディングする必要があります。それを構築するのは危険でトリッキーです。

単純にhigh scaleネットワーキングが必要な場合は、BeginXYZとファミリを引き続き使用し、APCを使用しているため、うまく機能することを確信できます。スレッドと.NETの特定の実装との間のマーシャリングにかかる​​費用はわずかです。

差出人: http://msdn.Microsoft.com/en-us/magazine/cc300760.aspx

サーバーのスケールアップの次のステップは、非同期I/Oを使用することです。非同期I/Oは、スレッドを作成および管理する必要性を軽減します。これにより、コードがはるかにシンプルになり、より効率的なI/Oモデルになります。非同期I/Oは、コールバックを利用して受信データと接続を処理します。つまり、設定とスキャンを行うリストがなく、保留中のI/Oを処理するために新しいワーカースレッドを作成する必要がありません。

興味深い副次的な事実として、シングルスレッドは、完了ポートを使用してWindowsで非同期ソケットを実行する最速の方法ではないということです。 http://doc.sch130.nsc.ru/www.sysinternalsを参照してください。 .com/ntw2k/info/comport.shtml

サーバーの目標は、複数のスレッドを使用して並列処理を最大化しながら、スレッドに不要なブロックを回避させることで、コンテキストスイッチをできるだけ少なくすることです。理想は、すべてのプロセッサでクライアント要求をアクティブに処理するスレッドがあり、それらのスレッドが要求を完了するときに待機している追加の要求がある場合にブロックしないことです。ただし、これが正しく機能するためには、クライアントのリクエストを処理している1つがI/Oでブロックされている場合(処理の一部としてファイルから読み取る場合など)、アプリケーションが別のスレッドをアクティブにする方法が必要です。

40
Sam Saffron

必要なのは、キューの次のタスクを実行する「メッセージループ」です。さらに、すべてのタスクをコーディングして、ブロックせずにできるだけ多くの作業を完了し、追加のタスクをキューに入れて、後で時間のかかるタスクを選択する必要があります。これには魔法のようなことはありません。ブロッキング呼び出しを使用したり、追加のスレッドを生成したりしないでください。

たとえば、HTTP GETを処理する場合、サーバーは、ソケットで現在使用可能な量のデータを読み取ることができます。これでリクエストを処理するのに十分なデータがない場合は、新しいタスクをエンキューして、将来再びソケットから読み取るようにします。 FileStreamの場合、インスタンスのReadTimeoutを低い値に設定し、ファイル全体よりも少ないバイト数を読み取る準備をしたいとします。

C#5は実際にこのパターンをはるかに簡単にします。多くの人は 非同期機能 がマルチスレッドを意味すると考えていますが、- それはそうではありません です。 asyncを使用すると、本質的に、それを管理するための複雑さを伴うことなく、前述のタスクキューを取得できます。

17
Chris Pitman

はい、それは Manos de mono と呼ばれています

真剣に、マノスの背後にある全体のアイデアは、シングルスレッドの非同期イベント駆動型Webサーバーです。

高性能でスケーラブル。友人のフィードを強化するテクノロジーであるトルネードウェブをモデルにしたManosは、何千もの同時接続が可能で、サーバーとの永続的な接続を作成するアプリケーションに最適です。

このプロジェクトはメンテナンスが少ないようで、おそらく本番稼働の準備が整っていないようですが、これが可能であることを示すための良いケーススタディになります。

10
Raynos

IO Completion Portsとは何か、およびC#を介してそれらにアクセスする方法を説明する素晴らしい記事シリーズです(つまり、Kernel32.dllからWin32 API呼び出しにPInvokeする必要があります)。

注: libuv クロスプラットフォームIO node.jsの背後にあるフレームワークは、WindowsではIOCP、UNIXオペレーティングシステムではlibevを使用します。

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

7
mythz

kayak それは基本的にPythonに対するC#の回答です twisted 、JavaScript node.js またはRuby eventmachine =

2
martyglaubitz

私はそのようなアーキテクチャの私自身の単純な実装をいじっていて、それを 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");

   });

それについての詳細は、 here を参照してください。

1
Ben Lesh

MVC、WebApi、ルーティングをサポートするHttpListenerとイベントループに基づくサーバーを開発しました。パフォーマンスは標準のIIS + MVCよりもはるかに優れています。MVCMusicStoreの場合、1秒あたり100リクエスト、100%CPUから30%CPUの350に移動しました。誰かがそれを試してみると私はフィードバックのために苦労しています!実際には、この構造に基づいてWebサイトを作成するためのテンプレートが存在します。

絶対に必要になるまで、ASYNC/AWAITを使用しないことに注意してください。私が使用する唯一のタスクは、ソケットへの書き込みやファイルの読み取りなど、I/Oバウンド操作用のタスクです。

PS提案や修正は大歓迎です!

1
Kendar

このフレームワーク SignalR およびこれについて Blog についてできます

0
iomar

まずSynchronizationContextについて。サムが書いたようなものです。基本クラスはシングルスレッド機能を提供しません。おそらく、UIスレッドでコードを実行する機能を提供するWindowsFormsSynchronizationContextからそのアイデアを得たでしょう。

ここで詳細を読むことができます

ThreadPoolパラメータで動作するコードを書きました。 (もう一度サムが指摘したことです)。

このコードは、フリースレッドで実行される3つの非同期アクションを登録します。それらの1つがThreadPoolパラメーターを変更するまで、それらは並行して実行されます。次に、各アクションが同じスレッドで実行されます。

.netアプリに強制的に1つのスレッドを使用できることを証明するだけです。 1つのスレッドのみで呼び出しを受信して​​処理するWebサーバーの実際の実装は、まったく異なるものです:)。

これがコードです:

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
Grzegorz W

LibuvSharp は、非同期IOのnode.jsプロジェクトで使用されるlibuvのラッパーです。ただし、低レベルのTCP/UDP /パイプ/タイマー機能のみが含まれています。そして、それはそのようにとどまるでしょう、その上にウェブサーバーを書くのは全く別の話です。これはudp上の単なるプロトコルであるため、DNS解決をサポートしていません。

0
Andrius Bentkus

私はそれが可能であると信じています、これはVB.NETとC#で書かれたオープンソースの例です:

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

イベントベースの非同期パターン(EAP)、IAsyncResultパターン、スレッドプール(IOCP)を使用します。メッセージ(クラスインスタンスなどのネイティブオブジェクトの場合があります)をバイナリパケットにシリアル化/マーシャリングし、TCP経由でパケットを転送してから、受信側でパケットをデシリアライズ/アンマーシャリングして、ネイティブオブジェクトを操作できるようにします。この部分は、ProtobufまたはRPCに多少似ています。

もともとはリアルタイムマルチプレイヤーゲームの「ネットコード」として開発されましたが、多くの目的に使用できます。残念ながら、私はそれを使うことに慣れませんでした。多分誰かがします。

ソースコードにはたくさんのコメントがあるので、わかりやすいはずです。楽しい!

0
perry

ここでは、オペレーティングシステムからの何らかのサポートが不可欠です。たとえば、Monoは非同期I/Oを備えたLinuxでepollを使用しているため、非常によく拡張できます(まだスレッドプール)。あなたが探していて、パフォーマンスとスケーラビリティがあるなら、間違いなくそれを試してください。

一方、あなたが言及したアイデアに基づいたC#(ネイティブlibs付き)Webサーバーの例はManos de Monoです。プロジェクトは最近アクティブではありません。ただし、アイデアとコードは一般に入手可能です。 this をお読みください(特に「マノスの詳細」の部分)。

編集:

メインスレッドでコールバックを発生させたいだけの場合は、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);
        }
    }
}

あなたが望むものを印刷します。さらに、ディスパッチャーで優先度を設定できます。しかし、同意してください、これは醜くハックであり、デモのリクエストに答える以外の理由でなぜ私がそうするのか分かりません;)

0

SingleSand と呼ばれるイベントループWebサーバーのもう1つの implementation を次に示します。シングルスレッドイベントループ内ですべてのカスタムロジックを実行しますが、Webサーバーはasp.netでホストされます。質問に答えると、.NETマルチスレッドの性質上、純粋なシングルスレッドアプリを実行することは通常不可能です。別のスレッドで実行される some アクティビティがあり、開発者はその動作を変更できません。

0
neleus