6

新しいスレッドを作成して関数を実行するコードから、スレッド プールまたはタスク並列ライブラリを使用するコードに変換しようとしています。私がこれを行っているのは、ワーカー スレッドの関数が (理論的には) 無期限に実行される可能性があるにもかかわらず、各スレッドがほとんどの時間を何もせずに費やすことを知っているからです。また、接続がタイムアウトしたり、新しい接続が作成されたりする可能性があるため、ワーカー スレッドの作成と破棄のオーバーヘッドを最小限に抑える方法も必要です。それ - そして CLRProfiler が 7836 個のスレッドが 62 時間のテスト実行で/後にファイナライズされたことを示すのを見るのは少し不安で、単一の (気難しい場合) デバイスがメッセージを送信しています。

これが私がやりたいことです:

メインスレッド。

1.) TCPListener に TcpClient を受け入れさせる

2.) その TcpClient を使用するワーカー スレッドを起動する

3.) 停止するように指示されていない場合は、ステップ 1 に戻ります。

ワーカー スレッド (プール/タスクで使用するため)

1.) TcpClient からのメッセージがあるかどうかを確認します

2.) その場合、メッセージを解析し、データベースに送信し、1 秒間スリープします。

3.) それ以外の場合は、1 ミリ秒間スリープします。

4.) 停止するように指示されておらず、タイムアウトしていない場合は、手順 1 に戻ります。

元のアプローチは次のとおりです。

private AutoResetEvent connectionWaitHandle = new AutoResetEvent(false);
private static bool stop = false;

private void MainThread()
{
    TcpListener universalListener = new TcpListener(IPAddress.Any, currentSettings.ListeningPort);
    universalListener.Start();

    while (!stop)
    {
        IAsyncResult result = universalListener.BeginAcceptTcpClient(WorkerThread, universalListener);
        connectionWaitHandle.WaitOne();
        connectionWaitHandle.Reset();
    }
}

private void WorkerThread(IAsyncResult result)
{
    TcpListener listener = result.AsyncState as TcpListener;

    if (listener == null)
    {
        connectionWaitHandle.Set();
        return;
    }

    TcpClient client = listener.EndAcceptTcpClient(result);
    connectionWaitHandle.Set();

    NetworkStream netStream = null;

    bool timedout = false;

    try
    {
        while (!timedout && !stop)
        {
            if (client.Available > 0)
            {
                netStream = client.GetStream();

                //Get and Parse data here, no need to show this code
                //The absolute fastest a message can come in is 2 seconds, so we'll sleep for one second so we aren't checking when we don't have to.
                Thread.Sleep(1000);
            }
            else
            {
                //Sleep for a millisecond so we don't completely hog the computer's resources.
                Thread.Sleep(1);
            }

            if (/*has timed out*/)
            {
                timedout = true;
            }
        }
    }
    catch (Exception exception)
    {
        //Log Exception
    }
    finally
    {
        client.Close();
    }
}

私は、universalListener.BeginAcceptTcpClient(...) などを置き換えてみました。すべてと

(new Task.TaskFactory.FromAsync<TCPClient>(universalListener.BeginAcceptTcpClient, universalListener.EndAcceptTcpClient, universalListener).ContinueWith(WorkerThread);

AutoResetEvent connectionWaitHandle コードを削除するだけでなく、ワーカー スレッドが 1 回だけ起動するように見えました。

また、スレッド プールとタスク (公式ドキュメントなど) について見つけたものはすべて、それらが極端な寿命が短い。

私の質問は次のとおりです。

  1. スレッドプールまたはタスク並列ライブラリのタスクでさえ、長寿命のスレッドに適していますが、ほとんどがホイールスピンですか?
  2. もしそうなら、どうすれば正しいパターンを実装するのが最善でしょうか?
  3. もしそうなら、私は TaskFactory.FromAsync(...).ContinueWith(...) の使用について正しい考えを持っていましたか?
4

1 に答える 1

6

デフォルトでは、TPLはスレッドプールを使用します。したがって、どちらの方法でもスレッドプールを使用しています。問題は、プールへのアクセスに使用するプログラミングモデルだけです。TPLは優れたプログラミング抽象化を提供するため、強くお勧めします。

この例のスレッドは、実際には回転していません(CPUサイクルを消費しています)が、待機ハンドルでブロックしています。これは非常に効率的で、ブロックされている間はスレッドを消費しません。

アップデート

TaskFactory.FromAsync(...)。ContinueWith(...)パターンが適切です。理由のすばらしいリストについては、この質問を参照してください。

C#5 / .NET 4.5を使用している場合は、async / awaitを使用して、コードパターンをさらにコンパクトに表現できます。

http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2010/11/22/c-5-0-rise-of-the-task.aspx

于 2012-11-12T20:37:41.587 に答える