2

わかりました、私は非同期/待機のすべてを理解したと思います。あなたが何かを待つときはいつでも、あなたが実行している関数が戻り、非同期関数が完了する間、現在のスレッドが何か他のことをすることを可能にします。利点は、新しいスレッドを開始しないことです。

Nodeがこれを実現するために多くのコールバックを使用することを除いて、Node.JSがどのように機能するかは多少異なるため、これを理解するのはそれほど難しくありません。しかし、これは私が利点を理解できないところです。

ソケットクラスには現在、Asyncメソッド(async / awaitで動作する)はありません。もちろん、ソケットをストリームクラスに渡し、そこで非同期メソッドを使用することもできますが、これでは新しいソケットの受け入れに問題が残ります。

私の知る限り、これを行うには2つの方法があります。どちらの場合も、メインスレッドの無限ループで新しいソケットを受け入れます。最初のケースでは、受け入れるすべてのソケットに対して新しいタスクを開始し、そのタスク内でstream.ReceiveAsyncを実行できます。ただし、タスクは他に何もすることがないので、awaitは実際にそのタスクをブロックしませんか?これもまた、スレッドプールでより多くのスレッドが生成される結果になりますが、これもタスク内で同期メソッドを使用するよりも優れていますか?

私の2番目のオプションは、受け入れられたすべてのソケットをいくつかのリストの1つ(スレッドごとに1つのリスト)に入れ、それらのスレッド内でループを実行し、すべてのソケットに対してawaitstream.ReceiveAsyncを実行することです。このようにして、awaitに遭遇するたびに、stream.ReceiveAsyncを実行し、他のすべてのソケットからの受信を開始します。

私の本当の質問は、これがスレッドプールよりも効果的かどうか、そして最初のケースでは、APMメソッドを使用するよりも本当に悪いかどうかだと思います。

また、await / asyncを使用してAPMメソッドを関数にラップできることも知っていますが、私が見る限り、async / awaitのステートマシンの余分なオーバーヘッドにより、APMメソッドの「デメリット」が発生します。

4

2 に答える 2

3

非同期ソケットAPIはに基づいていないため、 /Task[<T>]から直接使用することはできませんが、かなり簡単にブリッジできます。たとえば、(完全にテストされていません)。asyncawait

public class AsyncSocketWrapper : IDisposable
{
    public void Dispose()
    {
        var tmp = socket;
        socket = null;
        if(tmp != null) tmp.Dispose();
    }
    public AsyncSocketWrapper(Socket socket)
    {
        this.socket = socket;
        args = new SocketAsyncEventArgs();
        args.Completed += args_Completed;
    }

    void args_Completed(object sender, SocketAsyncEventArgs e)
    {
        // might want to switch on e.LastOperation
        var source = (TaskCompletionSource<int>)e.UserToken;
        if (ShouldSetResult(source, args)) source.TrySetResult(args.BytesTransferred);
    }
    private Socket socket;
    private readonly SocketAsyncEventArgs args;
    public Task<int> ReceiveAsync(byte[] buffer, int offset, int count)
    {

        TaskCompletionSource<int> source = new TaskCompletionSource<int>();
        try
        {
            args.SetBuffer(buffer, offset, count);
            args.UserToken = source;
            if (!socket.ReceiveAsync(args))
            {
                if (ShouldSetResult(source, args))
                {
                    return Task.FromResult(args.BytesTransferred);
                }
            }
        }
        catch (Exception ex)
        {
            source.TrySetException(ex);
        }
        return source.Task;
    }
    static bool ShouldSetResult<T>(TaskCompletionSource<T> source, SocketAsyncEventArgs args)
    {
        if (args.SocketError == SocketError.Success) return true;
        var ex = new InvalidOperationException(args.SocketError.ToString());
        source.TrySetException(ex);
        return false;
    }
}

注:おそらく、受信をループで実行することは避けてください。データを受信するときに、各ソケットがそれ自体をポンピングする責任を負うようにすることをお勧めします。すべてのソケットの死が検出できるわけではないため、ループが必要なのはゾンビを定期的にスイープすることだけです。

また、生の非同期ソケットAPIは、なくても完全に使用できることにも注意してくださいTask[<T>]。私はそれを広範囲に使用しています。awaitここで使用できる場合もありますが、必須ではありません。

于 2013-02-25T10:34:39.173 に答える
2

Nodeがこれを実現するために多くのコールバックを使用することを除いて、Node.JSがどのように機能するかは多少異なるため、これを理解するのはそれほど難しくありません。しかし、これは私が利点を理解できないところです。

Node.jsはコールバックを使用しますが、これらのコールバックを実際に単純化するもう1つの重要な側面があります。それらはすべて、同じスレッドにシリアル化されます。したがって、.NETで非同期コールバックを検討している場合、通常はマルチスレッドと非同期プログラミング( EAPスタイルのコールバックを除く)を扱っています。

コールバックを使用した非同期プログラミングは、「継続渡しスタイル」(CPS)と呼ばれます。これはNode.jsの唯一の実際のオプションですが、.NETの多くのオプションの1つです。特に、CPSコードは非常に複雑になり、保守が困難になる可能性があるため、async/awaitコンパイラ変換が導入されたため、「通常の外観」のコードを記述でき、コンパイラがそれをCPSに変換します。

どちらの場合も、メインスレッドの無限ループで新しいソケットを受け入れます。

サーバーを作成している場合は、はい、どこかで新しいクライアント接続を繰り返し受け入れます。また、接続されている各ソケットから継続的に読み取る必要があるため、各ソケットにもループがあります。

最初のケースでは、受け入れるすべてのソケットに対して新しいタスクを開始し、そのタスク内でstream.ReceiveAsyncを実行できます。

新しいタスクは必要ありません。それが非同期プログラミングの要点です。

私の2番目のオプションは、受け入れられたすべてのソケットをいくつかのリストの1つ(スレッドごとに1つのリスト)に入れ、それらのスレッド内でループを実行し、すべてのソケットに対してawaitstream.ReceiveAsyncを実行することです。

複数のスレッドが必要な理由、または専用のスレッドが必要な理由はわかりません。

asyncあなたはどのようにそしてどのように働くかについて少し混乱しているようですawait私自身の紹介MSDNの概要タスクベースの非同期パターンのガイダンスasyncFAQをこの順序で読むことをお勧めします。

また、await / asyncを使用してAPMメソッドを関数にラップできることも知っていますが、私が見る限り、async / awaitのステートマシンの余分なオーバーヘッドにより、APMメソッドの「デメリット」が発生します。

あなたがどのような不利益を言っているのかわかりません。ステートマシンのオーバーヘッドはゼロではありませんが、ソケットI/Oに直面しても無視できます。


ソケットI/Oを実行する場合は、いくつかのオプションがあります。読み取りの場合、APMを使用して「無限」ループで実行するか、 APMまたはAsyncメソッドTaskのラッパーを実行できます。または、RxまたはTPL Dataflowを使用して、それらをストリームのような抽象化に変換することもできます。

もう1つのオプションは、数年前に作成したNito.Asyncというライブラリです。すべてのスレッドマーシャリングを処理するEAPスタイル(イベントベース)のソケットを提供するため、Node.jsのような単純なものになります。もちろん、Node.jsのように、この単純さは、より複雑なソリューションと同様に拡張できないことを意味します。

于 2013-02-25T17:11:42.663 に答える