Please enable JavaScript.
Coggle requires JavaScript to display documents.
Advanced threading in .NET - Coggle Diagram
Advanced threading in .NET
Thread fundamentals
Thread fundamentals
Threads have space & time overhead as any other virtualization technology.
Kernel object(thread state, time left)
Context size x86~700bytes, x64~1240bytes
User-mode data(Thread env block)
4KB
Unhandled exception
terminates the application.
Stacks: user-mode(1MB committed) & kernel-mode(12KB/24KB)
DLL thread attach/detach notifications - method in dlls loaded in app need to called
Every context switch requires that Windows:
Save registers from CPU to running thread's kernel object
Determine which thread to schedule next
Load registers from selected thread's kernel object into CPU
After the switch, CPU suffers cache misses repopulating its cache
No performance increase using threads in 1 CPU!!!
Early OSes didn't support threads.
Processes were created to virtualize memory,
threads to virtualize CPU.
OS switches from one application's thread to another for a quantum(period ~30msec).
CPU can do one thing at a time and OS decides what to.
Windows schedules threads
All threads "appear" to run simultaneously
Windows schedules a thread to each CPU
Windows allows a thread to run for a time quantum
When quantum expires windows performs a context switch
A thread can voluntarily ends quant time earlier(waiting input etc) (e.g. process input keystroke took 10ms while quantum 40)
Windows won't schedule a waiting thread
In reality most of threads in the system are waiting something
How many threads ideally? A: Thread per CPU. Impossible in reality, because CLR creates 3 threads for application
Robust and responsive vs performance
Create and start a thread
Looping array of thousand elements if fraction of seconds
How many threads for 32bit? 1521. 64bit? Address space=8Tb
When debugging each step holds threads
Garbage collector stops threads
Avoid Thread.Sleep - waisting resources -> Use timer
Thread priority:
Highest, Above normal, Normal, Below normal, Lowest
Windows schedules threads to CPUs from Highest->Lowest
It's typically better to lower thread's priority:
Long running compute bound tasks
Won't adversely affect other processes
Avoid raising threads priority for tasks to execute smth for a very short time, react immediately for smth, and then block
Compute-bound async operations
Task - operation which will be complted in the future
Don't block threads!
Why? Waste resources(kernel obj, TEB, user/kernel stack etc)
you tend to create more threads
deep stack hurts GC, debugging perf
Avoid by performing async operations:
Compute bound - thread on CPU to do the work
I/O bound(in most cases) - Device driver has hardware to do the work. No threads required
The CLR's Thread Pool
manages thread creation for you, initially 0
TP has queue of async op requests - work items - delegates reffering to callback methods
TP thread extracts work item and executes call back method
When methods returns next work item dequeues - thread doesn't die
Queued work items are serviced by one thread
If work items queue quickly more threads are created
If work items reduce thread sit idle, but after a minutes wakes and kills itself
Queueing work item
ThreadPool.QueueUserWorkItem throws OUME
The standard cooperative cancelation pattern:
var cts = new CancelationToken(TimeSpan.FromSeconds(5));
ThreadPool.QueueUserWorkItem(Compute, cts);
...
cts.Cancel();
void Compute(obj){
while(){
if(((CancelationToken)obj).IsCancelationRequested)
}
}
Cancelation token is just a boolean past to thread
System.Threading.Tasks
//Following two lines are equal
ThreadPool.QueueUSerWorkItem(Compute, 5);
new Task(Compute, 5).Start();
Why queue not used?
Can't get exception, result, compute finished running
Task may call child tasks
var parent = new Task<int[]>(() => {
var result = new int[2];
new Task((() => result[0] = Sum(100000)), TaskCreationOptions.AttachedToParent).Start();
new Task((() => result[1] = Sum(100000)), TaskCreationOptions.AttachedToParent).Start();
return result;});
parent.Start();
parent.ContinueWith(t => Array.ForEach(t.Result, r => Console.WriteLine(r)));
var solution = new Task((() =>{
var tf = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.AttachedToParent);
var D = tf.StartNew(() => Compile("D"));
var E = tf.StartNew(() => Compile("E"));
var F = tf.StartNew(() => Compile("F"));
var B = D.ContinueWith(t => Compile("B"));
var C = tf.ContinueWhenAll(new[] {E, F}, t => Compile("C")); var A = tf.ContinueWhenAll(new[] {C, B}, t => Compile("A"));}));
solution.Start();
task.Wait() - block thread
task.Result - internally calls Wait();
WaitAll,WaitAny - cause calling thread to be blocked
var t = new Task(...);
t.Start();
t.ContinueWith(task=>{ task.Result //
here won't be blocked
},
TaskContinuationOptions.OnlyRunToCompletion)
....
TaskContinuationOptions.OnlyOnFaulted
TaskContinuationOptions.OnlyOnCanceled
Task has collection of continue with tasks
Canceling task
void Compute(token)
....
token.ThrowIfCancelationRequested()
....
System.Threading.Parallel
Parralel.For()
Loose ordering
Parallel.Invoke(DoWork1, DoWork2, DoWork3)
Timer
static class TimerDemo{
private static Timer s_timer;
public static void Main() {
s_timer = new Timer(Status, null, Timeout.Infinite, Timeout.Infinite);
s_timer.Change(0, Timeout.Infinite);
}
private static void Status(Object obj) {
//Check status
s_timer.Change(2000, Timeout.Infinite);
return;
}
}
I/O bound async ops
var fs = new FileStream(.., FileOptions.Async);
await fs.ReadAsync();