.Net Threading
This is the low-level threading to support async operations. The framework classes provide higher level APIs (eg Begin/End Async). From .net 4.5, when almost everything can be made easily awaitable.
- .Net 1-3.5: msdn
- .Net 4+: Task Parallel Library and see here
Simple Threads
Thread t = new Thread(SlowMethod); //ThreadStart delegate (no parameters)
t.Start(); //start it
t.Join(); //wait for it to finish
Debug.WriteLine(Thread.CurrentThread.Name + " Child threads finished");
//ParameterizedThreadStart delegate (one "object" parameter)
Thread tp = new Thread(SlowMethodWithObjectParameter);
tp.Start(1000); //pass in object start
tp.Join();
//Strongly typed with a delegate
Thread td = new Thread(delegate() { SlowMethodWithParameter(2000); });
td.Start(); //start the delegate which starts the method
td.Join();
ThreadPool
//optimize with ThreadPool (WaitCallback must have one "object" parameter)
ThreadPool.QueueUserWorkItem(SlowMethodWithObjectParameter);
Async delegates
//simple async delegate
//private delegate string WorkerDelegate(string s);
//private string Worker(string s) { return s.ToUpper(); }
WorkerDelegate method = Worker;
IAsyncResult ar = method.BeginInvoke("test", null, null);
// do other stuff
string uppercased = method.EndInvoke(ar);
// ** or **
method.BeginInvoke("test", DoneCallback, null);
/*static void DoneCallback(IAsyncResult ar)
{Debug.WriteLine(((WorkerDelegate)ar.AsyncState).EndInvoke(ar));}*/
Threading notes
- To protect atomic operations from t.Abort(), call Thread.BeginCriticialRegion() (and End...())
- To stop child threads inheriting CurrentCulture, IPrincipal etc, call ExecutionContext.SuppressFlow() (also RestoreFlow())
- To change thread contexts, get instance - ec= ExecutionContext.Capture(), edit it and ExecutionContext.Run(ec, new ContextCallback(contxt), null);
- In 2.0, there's a SynchronizationContext (an abstraction over asp/winform thread models)
ctx = new SynchronizationContext.Current; ctx.Post(method, stateObject) //async - sync(blocking) version is .Send
- Forms 2.0: BackgroundWorker component: set DoWork delegate, handle RunWorkerCompleted event (and ProgressChanged event), call .RunWorkerAsync(). In worker, call worker.ReportProgress
Timers
System.Threading.Timer tm = new System.Threading.Timer(new TimerCallback(Tick), "tock", 0, 0);
//startTime - 0 to start immediately, period- 0 to execute once
System.Timers.Timer tm2 = new System.Timers.Timer(1000);
tm2.Elapsed += delegate { Debug.WriteLine("Tock"); };
tm2.Enabled = true;
//do something
tm2.Enabled = false; //stop it
//remember System.Windows.Forms.Timer only works in WinForms and is actually singlethreaded
Thread Sync
- Interlocked.Add() ( .Increment is ++) - simple arithmetic
- c# lock(_obj) {} (==vb SynLock). NB: don't lock public instances (this)- prefer private instances
- Monitor.Enter(this) try/finally{ Monitor.Exit(this) }
C# lock is internally Monitor. Monitor.TryEnter has timeout - Mutex operates across AppDomain and process (unlike Monitor/ lock) but is very slow (derived from WaitHandle, and a win32 wrapper)
Mutex m = new Mutex();
//or try Mutex.OpenExisting("NamedMutex");
if (m.WaitOne(1000, false))
{
try { /*work*/ }
finally { m.ReleaseMutex(); }
} - Semaphore class has a number of slots which can be filled (you can ,Release(numSlots)). 2.0
- ReaderWriterLock (and from 3.5 ReaderWriterLockSlim) allows mutiple reads but makes writes exclusive.
// Declare at the class level to be visible to all threads
static ReaderWriterLock rwl = new ReaderWriterLock();
static int resource = 0;
public static void Execute(int timeOut, int to)
{
//try
rwl.AcquireReaderLock(timeOut);
int read = resource;
//upgrading- save the cookie
LockCookie cookie = rwl.UpgradeToWriterLock(to);
//try
resource++;//work
//finally
rwl.DowngradeFromWriterLock(ref cookie);
//finally
rwl.ReleaseLock();
} - EventWaitHandle (AutoEvent and ManualEvent). An event is unsignalled by one thread (.Set) and the others are blocked (on .WaitOne(timeout)) until it is signalled (.Reset). ManualEvent is signalled until manually Reset, AutoEvent is unsignalled as soon as the next thread (on WaitOne) picks it up
Asynchronous Patterns
The async patterns are wait (waithandle or EndInvoke), polling (IsCompleted) and Callback
protected void Main()
{
Literal1.Text = "Load= " + Thread.CurrentThread.ManagedThreadId;
Literal2.Text = "Begin/End= " + TestAsync();
Literal3.Text = "Load= " + Thread.CurrentThread.ManagedThreadId;
Literal4.Text = "Polling= " + TestAsyncPolling();
Literal5.Text = "Load= " + Thread.CurrentThread.ManagedThreadId;
Literal6.Text = "WaitHandle= " + TestAsyncWaitHandle();
TestAsyncCallback();
Literal7.Text = "Callback= " + _result;
Literal8.Text = "Load= " + Thread.CurrentThread.ManagedThreadId;
}
//a normal method doing something slow
public int SlowMethod(int sleep)
{
Thread.Sleep(sleep);
return Thread.CurrentThread.ManagedThreadId;
}
//delegate with same signature
public delegate int SlowMethodDelegate(int sleep);
public int TestAsync()
{
SlowMethodDelegate dg = SlowMethod; //create delegate
IAsyncResult ar = dg.BeginInvoke(2000, null, null); //start
Thread.Sleep(1000); //possibly do other work
return dg.EndInvoke(ar); //block until done
}
public int TestAsyncPolling()
{
SlowMethodDelegate dg = SlowMethod; //create delegate
IAsyncResult ar = dg.BeginInvoke(2000, null, null); //start
while (!ar.IsCompleted)
{
Thread.Sleep(500); //possibly do other work
}
return dg.EndInvoke(ar);
}
public int TestAsyncWaitHandle()
{
SlowMethodDelegate dg = SlowMethod; //create delegate
IAsyncResult ar = dg.BeginInvoke(2000, null, null); //start
Thread.Sleep(500); //possibly do other work
ar.AsyncWaitHandle.WaitOne(); //block until done
return dg.EndInvoke(ar);
}
private int _result;
public void TestAsyncCallback()
{
SlowMethodDelegate dg = SlowMethod; //create delegate
IAsyncResult ar = dg.BeginInvoke(2000, CallBack, dg); //start
//end
}
private void CallBack(IAsyncResult ar)
{
SlowMethodDelegate dg = (SlowMethodDelegate) ar.AsyncState; //our delegate back
_result = dg.EndInvoke(ar);
}