Performance merk je alleen als je machine meerdere cores heeft
Soort van proces, maar dan lightweight
Een thread krijgt werk mee, namelijk een stukje code. Als het werk klaar is, is de thread klaar. Werk wordt onafhankelijk van elkaar uitgevoerd
Als er 2 threads op 1 core draaien, vindt er thread switching plaats
Voor elke CPU (core) wordt er bijgehouden:
Als er een thread switch plaatsvindt

Scheduler van het OS regelt thread switching
In dotnet kan je een thread aanmaken, maar het OS is degene die de scheduling voor je doet
We hebben de volgende statements:
public class BankAccount
{
public string Name { get; set; }
public decimal Balance { get; set; }
}
BankAccount account = new BankAccount()
{
Name = "Marco",
Balance = 12.90m
};
In de applicatie is er een thread van tante Truus die eens per jaar 10 euro op de rekening stort:
Soms gebeurt dit wel, soms gebeurt dit niet. Alleen wanneer de scheduler toevallig op het verkeerde moment de threadswitch laat plaatsvinden.
Het feit dat de 1.000.000 is overschreven door de 22,90 noemen we een ‘race condition’.
Deze threads houden de applicatie in leven.
Deze threads houden de applicatie niet in leven.
private static object lockDing = new object();
lock (lockDing)
{
// Do work...
}
Een lock wordt onderwater Monitor.Enter(...) met daaronder een Monitor.Exit(...).
Het is verstandig om een lock object private voor de class te houden, zodat er niet vanuit andere plekken op hetzelfde object gelockt kan worden.
lock (this)
{
// Dont lock on this!
}
Lock nooit op this. Andere objecten hebben een referentie naar jou en zouden dus op jouw bitje kunnen locken
private static EventWaitHandle vlaggetje = new AutoResetEvent(false); // Non-signalled
private static void DoSomething()
{
for(int i = 0; i < 1000; i++)
{
if(i == 200)
{
vlaggetje.WaitOne();
}
}
}
private static void Main(string[] args)
{
for(int i = 0; i < 1000; i++)
{
vlaggetje.Set();
}
}
vlaggetje wordt op unsignalled gezetDoSomething arriveert bij de WaitOne() en moet wachten totdat het vlaggetje op signalled komt te staanMain zet het vlaggetje op signalled door de Set() aan te roepenDoSomething mag door
unsignalled gezet, doordat het vlaggetje een AutoResetEvent is (De naam zegt het al)AutoResetEvent handle = new AutoResetEvent(false);
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = ".";
watcher.Created += (object sender, FileSystemEventArgs e) =>
{
handle.Set();
};
File.WriteAllText(".\\test.txt", "Hello, world");
bool signalReceived = handle.WaitOne(1000);
Assert.IsTrue(signalReceived);
In het bovenstaande stukje code is een unit test te zien die controleert of het Created event aangeroepen wordt. Hier wordt een WaitHandle gebruikt om te kijken of de file al geschreven is. Gebeurt dit niet binnen de time-out van 1000 ms, wordt signalReceived de waarde false. Anders wordt deze true. Vervolgens kan hier gemakkelijk op ge-assert worden.
De state moet handmatig op unsignalled gezet worden.
Verschillende threads worden dus doorgelaten bij een WaitOne() tot dat de ManualResetEvent op unsignalled gezet wordt.
i++ is geen ondeelbare actie. Deze operatie gaat als volgt:
i wordt opgehaald en naar de stack gekopieerdi zetteni gezet
i in de tussentijd gezet heeft, wordt dit overschreven door deze stapLocks kunnen dit probleem verhelpen. Echter kan hiervoor ook de Interlocked class gebruikt worden.
Interlocked maakt gebruik van CPU-level operaties die bijvoorbeeld het ophogen van een int al als ` operatie uit kunnen voeren
Interlocked.Increment(ref i);Enkele voorbeelden van Interlocked methodes:
Interlocked.Increment(...);Interlocked.Decrement(...);Interlocked.Exchange(...);Interlocked.Add(...);Deze methodes zijn er voor onder andere ints, longs, etc. Deze zijn er NIET voor onder andere decimals
FileStream heeft de methode Read(...) voor het uitlezen van files. Echter is er ook een BeginRead(...) beschikbaar. Deze doet het volgende:
public void DoSomething()
{
FileStream fs = ...;
IAsyncResult result = fs.BeginRead(byte[] buffer, null, null);
int aantalBytesGelezen = fs.EndRead(result);
Console.WriteLine(aantalBytesGelezen);
}
Bij de BeginRead(...) hoort een EndRead(...). Deze methode is blokkeert de thread totdat het werk van de BeginRead(...) voltooid is. In het bovenstaande stukje wordt dit geillustreerd:
BeginRead(...) wordt aangeroepen
EndRead(...) wordt aangeroepen met als parameter de IAsyncResult van de BeginRead(...)EndRead(...) en wordt weergegeven op het schermAls iets thread safe is, kan het veilig gebruikt worden vantuit meerdere threads
classes, objecten, methodes en fieldsHet .NET framework komt met thread safe collections. Voorbeelden hiervan zijn:
ConcurrentDictionary<TKey, TValue>ConcurrentQueue<T>ConcurrentStack<T>Concurrent collections zorgen ervoor dat je iets trager bent, omdat er bij elke operatie een lock op de collection wordt.
System.Threading.CountdownEvent
Wait() gebruikt worden om de thread te blokkeren tot de counter bij 0 is.System.Threading.Barrier
Hek waar threads aankomen. Als alle threads er zijn, gaat de barrier weg en gaat alles tegelijk door.
SignalAndWait()System.Threading.SpinLock
Lightweight versie van Monitor (lock(ding) { ... })
spinLock.Enter(ref lockObject) voor het locken (waarbij lockObject een boolean is)spinLock.Exit(false) voor het vrijgeven van de lockWanneer de Thread class gebruikt wordt, wordt het werk dat gedaan moet worden in de threadpool gezet. Dit gebeurt zodra het thread object aangemaakt wordt.
Als alle threads bezig zijn en er komt meer werk, maakt de threadpool er threads bij (er zit wel een max op). Hier zal dus threadswitching plaatsvinden.
Een task is onder water zo’n ‘administratie object’ als IAsyncDing
Task task = Task.Factory.StartNew(() =>
{
// This would be work which takes a long time
Console.WriteLine("Working");
});
task.Start();
Een task zonder resultaat
Task<int> task = Task.Factory.StartNew(() =>
{
// This would be work which takes a long time
return 42;
});
Console.WriteLine(task.Result);
Een task met resultaat
Thread is duur om aan te maken tegenover een Task. Het volgende moet gebeuren om een Thread aan te maken:
Task functie in delegate stoppen en klaar Als de task gestart wordt, komt die in de queue
Task<double>[] tasks = new Task<double>[]
{
Task<double>.Factory.StartNew(() => DoSomething()),
Task<double>.Factory.StartNew(() => DoSomething2())
};
// Wait for all tasks with a time-out of 5000 ms
bool isDone = Task.WaitAll(tasks, 5000);
// Wait for 10000 ms and count how many tasks are done
int numberOfFinishedTasks = Task.WaitAny(tasks, 10000);
Wanneer de Task<T> class gebruikt wordt, wordt het werk dat gedaan moet worden in de taskpool gezet.
Deze tasks:
Heeft een andere core geen werk meer, dan ‘steelt’ die werk uit een andere core waar meerdere tasks uitgevoerd worden.
Kan gezien worden als een paralelle versie van een for loop
// from 0 to 1000
Parallel.For(0, 1000, (i) =>
{
Console.Write(i + " - ");
});
i de index.ParalellLoopState loopState = new ParallelLoopState();
Parallel.For(0, 5, (index, loopstate) => Process(index, loopState));
loopState.Break(); stop all iterations beyond current iteration loopState.Stop(); stop all iterations
Voer methodes tegelijkertijd uit
Parallel.Invoke(() => DoWork1(), () => DoWork2());
CancellationTokenSource cts = new CancellationTokenSource();
CencellationToken token = cts.Token;
Task myTask = Task.Factory.StartNew(() =>
{
// Infinite loop
for (; ; )
{
token.ThrowIfCancellationRequested();
...
}
}, token);
// More code...
cts.Cancel();
Voert queries parallel uit
Gebruik de methdoe AsParallel(...) om een query in parallel uit te voeren
var evenNumbers = from number in numbers.AsParallel()
where number % 2 == 0
select number;
De AsParallel(...) methode doet helemaal niets. Deze methode heeft de volgende parameter AsParallel(this IEnumerable<T> collection). Wat deze methode zo speciaal maakt, is het feit dat de returntype IParallelEnumerable<T>. Dit zorgt ervoor dat je niet de gewone where krijgt, maar een where uit de parallel enumerable universe. Dit geldt ook voor de andere extension methodes. Deze extension methodes doen alles parallel.
AsSequential(...) zorgt ervoor dat alles weer non-parallel/sequentieel gebeurt.
AsOrdered() te gebruiken.
var evenNumbers = from number in numbers
.AsParallel()
.AsOrdered()
where number % 2 == 0
select number;
Kost wel tijd, maar dan heb je ook wat. Een andere optie is om later een orderby te gebruiken:
var evenNumbers = from number in numbers.AsParallel()
where number % 2 == 0
orderby number
select number;
AsOrdered() niet.AggregateException.
.InnerExceptiontry
{
var evenNumbers = from number in numbers.AsParallel()
where number % 2 == 0
orderby number
select number;
}
catch(AggregateException e)
{
foreach(var exception in e.InnerExceptions)
{
Console.WriteLine(exception.Message);
}
}
Vroeger hadden we de Begin en End methodes. Zie file.BeginRead en file.EndRead (Klikker de klik)
async Task<int> AccessTheWebAsync()
{
HttpClient client = new HttpClient();
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
DoIndependentWork();
string urlContents = await getStringTask;
return urlContents.Length;
}
getStringTask begint al met het ophalen van http://msdn.microsoft.com in een andere thread.http://msdn.microsoft.com is nodig na de DoIndependentWork(). await wordt gebruikt om te wachten tot de getStringTask voltooid is.await is niet blocking
.Result() is blocking want gebeurt op zelfde thread.