I was trying to create an integration test for my service where 100 clients would connect, login, send request, and log all responses for some configurable amount of time.
I was build a class for the client using async sockets and it works fine. I started them all up using Task and Task.Factory, sent the login, and posted receives every time I got data, until the time expired and then I called shutdown on them.
It seems these never really run in parallel. Sometimes I might get 35ish running at once, sometimes a little more. I assume the task scheduler is running them when it seems fit rather than all at once.
Now I understand that I cannot truly have 100 threads running simultaneous, but I want to guarantee that all 100 are started and that the OS is context switching back and forth attempting to execute them all.
In the end, I want to simulate a large number of clients connected to my service all getting a stream of data.
What construct do I use if Task does not work?
Current Attempt:
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
}
}
}
}
Threadrather thanTask. It's the only way to guarantee it. Do note that operating systems can have limits on the number of threads, and too many threads per core can have the app spend so much time context switching it doesn't have time to actually run. – Berin Loritsch Dec 15 '17 at 21:11Thread.Sleep(1);in the loop to prevent 100 threads from melting your CPU for no reason. – Berin Loritsch Dec 15 '17 at 21:14