it-swarm.com.de

Welches Konstrukt verwende ich, um sicherzustellen, dass 100 Aufgaben parallel ausgeführt werden?

Ich habe versucht, einen Integrationstest für meinen Dienst zu erstellen, bei dem 100 Clients eine Verbindung herstellen, sich anmelden, eine Anfrage senden und alle Antworten für einen konfigurierbaren Zeitraum protokollieren.

Ich habe eine Klasse für den Client mit asynchronen Sockets erstellt und es funktioniert einwandfrei. Ich habe sie alle mit Task und Task.Factory gestartet, das Login gesendet und jedes Mal, wenn ich Daten erhalten habe, Empfangsbestätigungen gesendet, bis die Zeit abgelaufen ist, und dann habe ich das Herunterfahren für sie aufgerufen.

Es scheint, dass diese nie wirklich parallel laufen. Manchmal kann ich 35ish auf einmal zum Laufen bringen, manchmal etwas mehr. Ich gehe davon aus, dass der Taskplaner sie ausführt, wenn es eher fit als auf einmal erscheint.

Jetzt verstehe ich, dass ich nicht wirklich 100 Threads gleichzeitig ausführen kann, aber ich möchte garantieren, dass alle 100 gestartet werden und dass das Betriebssystem den Kontext hin und her wechselt, um sie alle auszuführen.

Am Ende möchte ich eine große Anzahl von Clients simulieren, die mit meinem Dienst verbunden sind und alle einen Datenstrom erhalten.

Welches Konstrukt verwende ich, wenn Task nicht funktioniert?

Aktueller Versuch:

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

Aufgaben und Themen existieren für verschiedene Zwecke. Aufgaben sollen kurz laufende Dinge sein, die im Hintergrund ausgeführt werden müssen. Threads stellen eine Betriebsressource für die gleichzeitige Ausführung dar.

Intern verwendet der TaskManager einen Thread-Pool, damit er Threads wiederverwenden kann, um weitere Aufgaben zu verarbeiten. Das Einrichten und Abreißen von Threads ist teuer, sodass sie für den Zweck, für den Aufgaben erstellt wurden, nicht gut funktionieren. Sie können zwar die Anzahl der Threads beeinflussen, die dem Task-Manager zur Verfügung stehen, er ist jedoch weiterhin dafür verantwortlich, die Arbeit an die Threads zu verteilen.

Garantiert X Anzahl gleichzeitiger Clients

Die einzige Möglichkeit, dies zu gewährleisten, besteht darin, Thread anstelle von Task zu verwenden. Wenn Sie Ihren Code ein wenig umstrukturieren würden, könnten Sie Ihre gleichzeitigen Clients wie folgt behandeln:

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

Ich glaube nicht, dass es ein Problem mit Ihrem Test gibt.

Ich habe ähnlichen Code für (grundlegende) Lasttests verwendet und weit über 100 gleichzeitige Aufgaben gesehen.

Ich würde vermuten, dass es ein Problem mit der Art und Weise gibt, wie Sie sich anmelden. Messen Sie einfach die Anzahl der gleichzeitigen Verbindungen, die Ihr Server verarbeiten kann?

Der folgende Code zählt beispielsweise bis zu 1000.

Beachten Sie jedoch den Unterschied, wenn wir Task.Delay durch Thread.Sleep ersetzen. Dadurch wird die App unterbrochen, da mehr als eine Aufgabe im selben Thread ausgeführt wird.

Nun, wenn wir auch die Aufgabe ändern. Hinzufügen zu:

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

Der Code funktioniert wieder, da er die neuen Aufgaben in neuen Threads erstellen kann

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