Reputation: 36054
I am creating an application where the client needs to find the server on the same network.
The server:
public static void StartListening(Int32 port) {
TcpListener server = new TcpListener(IP.GetCurrentIP(), port);
server.Start();
Thread t = new Thread(new ThreadStart(() =>
{
while (true)
{
// wait for connection
TcpClient client = server.AcceptTcpClient();
if (stopListening)
{
break;
}
}
}));
t.IsBackground = true;
t.Start();
}
Let's say the server is listening on port 12345
then the client:
create a list of all posible ip addresses. The server ip address will probably be related to the client's ip if they are on the same local network therefore I construct the list as:
192.168.5.0
192.168.5.1
192.168.5.2
192.168.5.3
.....etc
.....
192.168.0.88
192.168.1.88
192.168.2.88
192.168.3.88
...etc
192.0.5.88
192.1.5.88
192.2.5.88
192.3.5.88
192.4.5.88
..... etc
0.168.5.88
1.168.5.88
2.168.5.88
3.168.5.88
4.168.5.88
.... etc
Then I try to connect with every possible ip and port 12345. If one connection is successful then that means that I found the address of the server.
Now I have done this in two ways. I know just the basics about threads and I don't know if this is dangerous but it works really fast.
// first way
foreach (var ip in ListOfIps)
{
new Thread(new ThreadStart(() =>
{
TryConnect(ip);
})).Start();
}
the second way I belive it is more safe but it takes much more time:
// second way
foreach (var ip in ListOfIps)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(TryConnect), ip);
}
I have to call the TryConnect method about 1000 times and each time it takes about 2 seconds (I set the connection timeout to 2 seconds). What will be the most efficient and secure way of calling it 1000 times?
Here are the results using different techniques:
1) Using threadpool
..
..
var now = DateTime.Now;
foreach (var item in allIps)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), item);
}
ThreadPool.QueueUserWorkItem(new WaitCallback(PrintTimeDifference), now);
}
static void PrintTimeDifference(object startTime)
{
Console.WriteLine("------------------Done!----------------------");
var s = (DateTime)startTime;
Console.WriteLine((DateTime.Now-s).Seconds);
}
It took 37 seconds to complete
2) Using threads:
..
..
var now = DateTime.Now;
foreach (var item in allIps)
{
new Thread(new ThreadStart(() =>
{
DoWork(item);
})).Start();
}
ThreadPool.QueueUserWorkItem(new WaitCallback(PrintTimeDifference), now);
It took 12 seconds to complete
3) Using tasks:
..
..
var now = DateTime.Now;
foreach (var item in allIps)
{
var t = Task.Factory.StartNew(() =>
DoWork(item)
);
}
ThreadPool.QueueUserWorkItem(new WaitCallback(PrintTimeDifference), now);
}
static void PrintTimeDifference(object startTime)
{
Console.WriteLine("------------------Done!----------------------");
var s = (DateTime)startTime;
Console.WriteLine((DateTime.Now-s).Seconds);
}
It took 8 seconds!!
Upvotes: 1
Views: 6362
Reputation: 4041
Now I made my own benchmark.
Here is the code:
class Program {
private static long parallelIterations = 100;
private static long taskIterations = 100000000;
static void Main(string[] args) {
Console.WriteLine("Parallel Iterations: {0:n0}", parallelIterations);
Console.WriteLine("Task Iterations: {0:n0}", taskIterations);
Analyse("Simple Threads", ExecuteWorkWithSimpleThreads);
Analyse("ThreadPool Threads", ExecuteWorkWithThreadPoolThreads);
Analyse("Tasks", ExecuteWorkWithTasks);
Analyse("Parallel For", ExecuteWorkWithParallelFor);
Analyse("Async Delegates", ExecuteWorkWithAsyncDelegates);
}
private static void Analyse(string name, Action action) {
Stopwatch watch = new Stopwatch();
watch.Start();
action();
watch.Stop();
Console.WriteLine("{0}: {1} seconds", name.PadRight(20), watch.Elapsed.TotalSeconds);
}
private static void ExecuteWorkWithSimpleThreads() {
Thread[] threads = new Thread[parallelIterations];
for (long i = 0; i < parallelIterations; i++) {
threads[i] = new Thread(DoWork);
threads[i].Start();
}
for (long i = 0; i < parallelIterations; i++) {
threads[i].Join();
}
}
private static void ExecuteWorkWithThreadPoolThreads() {
object locker = new object();
EventWaitHandle waitHandle = new ManualResetEvent(false);
int finished = 0;
for (long i = 0; i < parallelIterations; i++) {
ThreadPool.QueueUserWorkItem((threadContext) => {
DoWork();
lock (locker) {
finished++;
if (finished == parallelIterations)
waitHandle.Set();
}
});
}
waitHandle.WaitOne();
}
private static void ExecuteWorkWithTasks() {
Task[] tasks = new Task[parallelIterations];
for (long i = 0; i < parallelIterations; i++) {
tasks[i] = Task.Factory.StartNew(DoWork);
}
Task.WaitAll(tasks);
}
private static void ExecuteWorkWithParallelFor() {
Parallel.For(0, parallelIterations, (n) => DoWork());
}
private static void ExecuteWorkWithAsyncDelegates() {
Action[] actions = new Action[parallelIterations];
IAsyncResult[] results = new IAsyncResult[parallelIterations];
for (long i = 0; i < parallelIterations; i++) {
actions[i] = DoWork;
results[i] = actions[i].BeginInvoke((result) => { }, null);
}
for (long i = 0; i < parallelIterations; i++) {
results[i].AsyncWaitHandle.WaitOne();
results[i].AsyncWaitHandle.Close();
}
}
private static void DoWork() {
//Thread.Sleep(TimeSpan.FromMilliseconds(taskDuration));
for (long i = 0; i < taskIterations; i++ ) { }
}
}
Here is the result with different settings:
Parallel Iterations: 100.000
Task Iterations: 100
Simple Threads : 13,4589412 seconds
ThreadPool Threads : 0,0682997 seconds
Tasks : 0,1327014 seconds
Parallel For : 0,0066053 seconds
Async Delegates : 2,3844015 seconds
Parallel Iterations: 100
Task Iterations: 100.000.000
Simple Threads : 5,6415113 seconds
ThreadPool Threads : 5,5798242 seconds
Tasks : 5,6261562 seconds
Parallel For : 5,8721274 seconds
Async Delegates : 5,6041608 seconds
As you can see simple threads are not efficient when there are too much of them. But when using some of them they are very efficient because there is little overhead (e.g. synchronization).
Upvotes: 2
Reputation: 273314
Both methods run the risk of creating way too many Threads.
A thread is expensive in the time it takes to be created and in memory consumption.
It does look like your 2nd approach, using the ThreadPool, should work better. Because of the long timeout (2 sec) it will still create many threads, but far less then 1000.
The better approach (requires Fx 4) would be to use Parallel.ForEach(...)
. But that too may require some tuning.
And a really good solution would use a broadcast (UDP) protocol to discover services.
Upvotes: 2
Reputation: 62449
Well there are pros and cons to this approach:
Using an individual thread per connection will (in theory) let you make all connections in parallel, since this is a blocking I/O operation all threads will be suspended until the respective connection succeeds. However, creating 1000 threads is a bit of an overkill on the system.
Using the thread pool gives you the benefit of reusing threads, but only a limited number of connection tasks can be active at one time. For example if the thread pool has 4 threads, then 4 connections will be attempted, then another 4 and so on. This is light on resource but may take too long because, as you said, a single connection needs about 2 seconds.
So I would advise a trade-off: create a thread-pool with about 50 threads (using the SetMaxThreads
method) and queue all the connections. That way, it will be lighter on resources than 1000 threads, and still process connections reasonably fast.
Upvotes: 1
Reputation: 4041
In this case I would prefer the solution with the ThreadPool-Threads, because creating 1000 Threads is a heavy operation (when you think of the memory each thread gets).
But since .NET 4 there is another solution with the class Task
.
Tasks are workloads which can be executed in parallel. You can define and run them like this:
var t = Task.Factory.StartNew(() => DoAction());
You don't have to care about the number of threads used because the runtime environment handles that. So if you have the possibility to split your workload into smaller packages which can be executed in parallel I would use Tasks to do the work.
Upvotes: 3