web-dev-qa-db-ja.com

100個のタスクが並行して実行されることを保証するために使用する構成体

私のサービスの統合テストを作成しようとしていましたが、100台のクライアントが接続し、ログインし、リクエストを送信し、構成可能な時間の間すべての応答をログに記録しました。

私は非同期ソケットを使用してクライアント用のクラスを作成していましたが、正常に機能します。私はそれらすべてをTaskとTask.Factoryを使用して起動し、ログインを送信し、時間が経過するまでデータを取得するたびにポストされた受信を受信し、その後それらにシャットダウンを呼び出しました。

これらが実際に並行して実行されることはないようです。 35ishを一度に実行することもあれば、もう少し実行することもあります。タスクスケジューラは、一度にすべてではなく、適切と思われるときにそれらを実行していると思います。

本当に100個のスレッドを同時に実行することはできないことを理解しましたが、100個すべてのスレッドが開始され、OSがそれらすべてを実行しようとして前後にコンテキストを切り替えることを保証したいと思います。

最後に、サービスに接続された多数のクライアントをシミュレートして、すべてデータのストリームを取得します。

タスクが機能しない場合、どのコンストラクトを使用しますか?

現在の試み:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IntegrationTests
{
    class Program
    {
        static void Main(string[] args)
        {
            string       server            = ConfigurationManager.AppSettings["server"];
            int          port              = int.Parse(ConfigurationManager.AppSettings["port"]);
            int          numClients        = int.Parse(ConfigurationManager.AppSettings["numberOfClients"]);
            TimeSpan     clientLifetime    = TimeSpan.Parse(ConfigurationManager.AppSettings["clientLifetime"]);
            TimeSpan     timeout           = TimeSpan.Parse(ConfigurationManager.AppSettings["timeout"]);
            TimeSpan     reconnectInterval = TimeSpan.Parse(ConfigurationManager.AppSettings["reconnectInterval"]);
            List<string> clientIds         = ConfigurationManager.GetSection("clientIds") as List<string>;

            try
            {
                // SNIP configure logging

                // Create the specified number of clients, to carry out test operations, each on their own threads
                Task[] tasks = new Task[numClients];
                for(int count = 0; count < numClients; ++count)
                {
                    var index = count;
                    tasks[count] = Task.Factory.StartNew(() =>
                        {
                            try
                            {
                                // Reuse client Ids, if there are more clients then clientIds.
                                // Keep in mind that tasks are not necessarily started in the order they were created in this loop.
                                // We may see client id 1 be assigned client id 2 if another client was started before it, but we
                                // are using all clientIds
                                string clientId = null;
                                if (numClients < clientIds.Count)
                                {
                                    clientId = clientIds[index];
                                }
                                else
                                {
                                    clientId = clientIds[index % clientIds.Count];
                                }

                                // Create the actual client
                                Client client = new Client(server, port, clientId, timeout, reconnectInterval);
                                client.Startup();

                                // Will make an sync request issue a recv.
                                // Everytime we get a reponse, it will be logged and another recv will be posted.
                                // This will continue until shutdown is called
                                client.MakeRequest(symbol);

                                System.Threading.Thread.Sleep(clientLifetime);

                                client.Shutdown();
                            }
                            catch(Exception e)
                            {
                                // SNIP - Log it
                            }
                        });
                }
                Task.WaitAll(tasks);
            }
            catch (Exception e)
            {
                // SNIP - Log it
            }
        }
    }
}
5

タスクとスレッドはさまざまな目的で存在します。タスクは、バックグラウンドで実行する必要がある短期間の実行を目的としています。スレッドは、同時実行のためのオペレーティングリソースを表します。

内部的には、TaskManagerはスレッドプールを使用して、スレッドを再利用してより多くのタスクを処理できるようにします。スレッドはセットアップと破棄にコストがかかるため、タスクが作成された目的ではうまく機能しません。タスクマネージャーが使用できるスレッドの数に影響を与えることができますが、それでもスレッドに作業を分配する責任があります。

保証X同時クライアント数

これを保証する唯一の方法は、Threadの代わりにTaskを使用することです。コードを少し再構築する場合は、次のように同時クライアントを処理できます。

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading;

namespace IntegrationTests
{
    private static string server;
    private static int port;
    private static TimeSpan clientLifetime;
    private static TimeSpan timeout;
    private static TimeSpan reconnectInterval;
    private static List<string> clientIds;
    private static Barrier barrier;

    class Program
    {
        static void Main(string[] args)
        {
            int          numClients        = int.Parse(ConfigurationManager.AppSettings["numberOfClients"]);
            server            = ConfigurationManager.AppSettings["server"];
            port              = int.Parse(ConfigurationManager.AppSettings["port"]);
            clientLifetime    = TimeSpan.Parse(ConfigurationManager.AppSettings["clientLifetime"]);
            timeout           = TimeSpan.Parse(ConfigurationManager.AppSettings["timeout"]);
            reconnectInterval = TimeSpan.Parse(ConfigurationManager.AppSettings["reconnectInterval"]);
            clientIds         = ConfigurationManager.GetSection("clientIds") as List<string>;
            barrier           = new Barrier(numClients + 1);

            try
            {
                // SNIP configure logging

                // Create the specified number of clients, to carry out test operations, each on their own threads
                Thread[] threads= new Thread[numClients];
                for(int count = 0; count < numClients; ++count)
                {
                    var index = count;
                    threads[count] = new Thread();
                    threads[count].Name = $"Client {count}"; // for debugging
                    threads[count].Start(RunClient);
                }

                // We loose the convenience of awaiting all tasks,
                // but use a thread barrier to block this thread until all the others are done.
                barrier.SignalAndWait();
            }
            catch (Exception e)
            {
                // SNIP - Log it
            }
        }

        private void RunClient()
        {
            try
            {
                // Reuse client Ids, if there are more clients then clientIds.
                // Keep in mind that tasks are not necessarily started in the order they were created in this loop.
                // We may see client id 1 be assigned client id 2 if another client was started before it, but we
                // are using all clientIds
                string clientId = null;
                if (numClients < clientIds.Count)
                {
                    clientId = clientIds[index];
                }
                else
                {
                    clientId = clientIds[index % clientIds.Count];
                }

                // Create the actual client
                Client client = new Client(server, port, clientId, timeout, reconnectInterval);
                client.Startup();

                // Will make an sync request issue a recv.
                // Everytime we get a reponse, it will be logged and another recv will be posted.
                // This will continue until shutdown is called
                client.MakeRequest(symbol);

                System.Threading.Thread.Sleep(clientLifetime);

                client.Shutdown();
            }
            catch(Exception e)
            {
                // SNIP - Log it
            }
            finally
            {
                barrier.SignalAndWait();
            }
        }
    }
}
7
Berin Loritsch

あなたのテストに問題はないと思います。

(基本的な)負荷テストに同様のコードを使用し、100以上の同時タスクを確認しました。

ロギングの方法に問題があると思います。サーバーが処理できる同時接続数を単に測定していますか?

たとえば、以下のコードは1000までカウントされます。

ただし、Task.DelayをThread.Sleepに置き換えた場合の違いに注意してください。同じスレッドで複数のタスクが実行されるため、アプリが破損します。

さて、もしalsoをtask.Addに変更すると:

tasks.Add(Task.Factory.StartNew(async () => Work(),TaskCreationOptions.LongRunning));

新しいスレッドで新しいタスクを作成することがわかっているため、コードは再び機能します

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Collections.Generic;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        volatile int count = 0;
        [TestMethod]
        public async Task TestMethod1()
        {
            var tasks = new List<Task>();
            for(int i = 0;i<1000;i++)
            {
                tasks.Add(Work());
            }
            await Task.WhenAll(tasks.ToArray());
            Debug.WriteLine("finished");
        }

        async Task Work()
        {
            count++;
            Debug.WriteLine(count);
            await Task.Delay(10000);
            Debug.WriteLine(count);
            count--;
        }
    }
}
1
Ewan