PROWAREtech
.NET: Working with Threads
Working with threads in .NET is practically identical to using them in the Windows API. The end result is identical to Tasks except that they are implemented slightly differently.
Most asynchronous functions support the CancellationToken
class so Tasks really are preferred. A thread can be cancelled through the use of a variable that is constantly being checked by the procedure. The same is happening with the CancellationToken
but .NET async
functions support the CancellationToken
.
Advantages of Threads Over Tasks
Threads seem to execute more quickly and in uniform order. Comparing Tasks performing the same operations, execution occurs in different order than the order in which they are created. This makes no difference for most apps.
There are not many advantages over tasks.
Creating and Starting a Thread
Using the .NET Thread
class, threads can be created and controlled. After a Thread
object is created, use the Start()
method.
using System;
using System.Threading;
class ThreadExample
{
static void Main()
{
var thread1 = new Thread(ThreadFunc);
thread1.Start();
Console.WriteLine("Main thread");
}
static void ThreadFunc()
{
Console.WriteLine("In a thread");
}
}
Passing Data to Threads
Parameter(s) can be passed to the new thread's method but it must be passed as an object
and then type cast to the original data type.
using System;
using System.Threading;
class ThreadExample
{
public struct Data
{
public string first;
public string last;
}
static void Main()
{
Data d;
d.first = "abc";
d.last = "xyz";
var thread1 = new Thread(ThreadFunc);
thread1.Start(d);
Console.WriteLine("Main thread");
}
static void ThreadFunc(object o)
{
Data d2 = (Data)o;
Console.WriteLine("In a thread with parameter: " + d2.first);
}
}
Background Threads
The application's process continues to run for as long as at least one foreground thread is running. If this is a problem then solve it by using background threads.
Specify the property IsBackground = true
to create background threads. Notice: on Linux, threads that are not background ones are like separate processes and do not end when the creating process ends. How to kill a process on Linux.
using System;
using System.Threading;
class ThreadExample
{
static void Main()
{
var thread1 = new Thread(ThreadFunc) { IsBackground = true };
thread1.Start();
Console.WriteLine("Main thread");
}
static void ThreadFunc()
{
Console.WriteLine("In a thread");
}
}
Solve π on Each Logical Processor
This version of finding pi does so with integers. See this article for an example of using the .NET Task
to find pi with floating point variables. FindPi_2
is the popular Bailey–Borwein–Plouffe formula.
For a C++ version of finding pi, see this article
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace ConsoleApp1
{
class JobThread
{
public int id { get; }
public ulong iterations { get; }
public Thread thread { get; }
public double pi { get; set; }
public bool cancel { get; set; }
public JobThread(int id, ulong iterations)
{
this.iterations = iterations;
this.id = id;
if((id & 1) == 0)
thread = new Thread(FindPi_2) { IsBackground = true };
else
thread = new Thread(FindPi_1) { IsBackground = true };
Console.WriteLine("Starting Job: {0, -" + Environment.ProcessorCount.ToString().Length + "} Iterations: {1}", id, iterations);
thread.Start(this); // pass this object to the thread
}
static void FindPi_1(object job) // this uses brute force to solve pi and it takes huge iterations to be as accurate as the formula below
{
//π = 3.14159265358979323846264338327950288419…
//π = (4/1) - (4/3) + (4/5) - (4/7) + (4/9) - (4/11) + (4/13) - (4/15) + ...
//π = 4 * (1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + 1/13 - 1/15 + ...)
long count = (long)((JobThread)job).iterations;
long x = 1000000000000000000;
for (long i = 2; !((JobThread)job).cancel & i < count; i++)
x += ((i & 1) == 0 ? -1000000000000000000 : 1000000000000000000) / ((i << 1) - 1);
((JobThread)job).pi = 4 * (x / 1000000000000000000.0);
Console.WriteLine("Job: {0, -" + Environment.ProcessorCount.ToString().Length + "} ended with pi={1}", ((JobThread)job).id, ((JobThread)job).pi.ToString("0.00000000000000"));
}
static ulong Power(ulong x, ulong y)
{
ulong m = 1;
for (ulong i = 0; i < y; i++)
m *= x;
return m;
}
static void FindPi_2(object job) // this is the fast, efficient and accurate Bailey–Borwein–Plouffe formula
{
//π = 3.14159265358979323846264338327950288419...
ulong count = ((JobThread)job).iterations;
ulong x = 0;
for (ulong k = 0, p = Power(16, k); (p > 0) & (k < count); k++, p = Power(16, k))
x += (4000000000000000000 / (8 * k + 1) - 2000000000000000000 / (8 * k + 4) - 1000000000000000000 / (8 * k + 5) - 1000000000000000000 / (8 * k + 6)) / p;
((JobThread)job).pi = (x / 1000000000000000000.0);
Console.WriteLine("Job: {0, -" + Environment.ProcessorCount.ToString().Length + "} ended with pi={1}", ((JobThread)job).id, ((JobThread)job).pi.ToString("0.00000000000000"));
}
}
class Program
{
static void Main(string[] args)
{
var jobs = new List<JobThread>();
Console.WriteLine("pi={0}", 3.1415926535897932384626433832795028841971693993751M.ToString("0.00000000000000"));
Console.WriteLine("Logical Processors: {0}", Environment.ProcessorCount);
Console.WriteLine("ENTER A JOB NUMBER TO TERMINATE IT AT ANYTIME");
var spaces = Environment.ProcessorCount.ToString().Length;
int[] jobsIds = new int[Environment.ProcessorCount];
for (int i = 0; i < Environment.ProcessorCount; i++)
jobsIds[i] = i;
var rnd = new Random();
foreach (var jobId in jobsIds)
{
ulong count = (ulong)rnd.Next() * 10;
jobs.Add(new JobThread(jobId, count));
}
Thread.Sleep(250);
var thread = new Thread(() =>
{
while (jobs.Where(j => j.thread.IsAlive == true).Count() > 0)
Thread.Sleep(250);
Environment.Exit(0);
})
{ IsBackground = true };
thread.Start();
while (jobs.Where(j => j.thread.IsAlive == true).Count() > 0)
{
var id = Console.ReadLine();
JobThread jt = jobs.Where(j => j.id.ToString() == id).FirstOrDefault();
if (jt != null)
jt.cancel = true;
}
}
}
}