Title: 'NET Async Programming Demystified
1.NET Async Programming Demystified
Keith Rome, MCSD MCAD MCDBA keith_at_mindfusioncorp.c
om http//www.mindfusioncorp.com/weblog/
2What is Async Programming?
- A method of executing long-running processes.
3When to use it?
- For Non-UI Applications / Components
- When you need to obtain better utilization of
processing resources. - For UI Applications / Components
- When you need to achieve a more responsive user
interface.
4When to NOT use it?
- If you cannot establish a realistic need.
- If the risk of introducing new bugs is not
tolerable. - Async programming is more complex and introduces
greater exposure to potential bugs. - If you dont know what you are getting into.
- Because you think its cool.
5Types of Async Processing
- From simplest to most complex
- Faking it
- External Processes
- Queuing
- Multithreading
- Each has its strengths and weaknesses choose
the one thats best for your situation.
6Faking Async Processing
- Use an animated graphic or other visual cue to
distract the user. - Regularly call Control.Update() to keep the UI
painting itself. - Sure, its cheesy but it often works and its
easy to implement!
7External Processes
- Kick off command-line executables.
- The ultimate in process isolation!
- If the background task crashes, your main program
is not directly affected. - Use Process.Start() to begin execution.
- Process.WaitForExit() can be used to block until
a spawned process returns.
8Message Queuing
- Based on MSMQ.
- Anything can be placed in a queue for later
processing - Strings
- XML Messages (SOAP or hand-rolled)
- Any serializable object
- Can use Xml (the default), Binary, or ActiveX
(VB6 interoperability) formatters.
9Message Queuing (cont.)
- Can attach to a queue on the local machine, or on
a remote machine. - Beware of security rights issues when creating
and connecting to queues. - Use MessageQueue.Send() to send (push) a message.
- Use MessageQueue.Receive() to read (pop) a
message. - There is also a BeginReceive() Async method to
read without blocking.
10Multithreading
- There are three categories of threads in .NET
- CLR Threads
- Maintained by the CLR
- No control over these
- One we all deal with is the main UI Pump
- ThreadPool threads
- Quick way to use multithreading
- Explicit threads
- Offer the most power, flexibility, and bugs.
11About the ThreadPool
- There is only one thread pool per Process.
- Many framework objects use the thread pool
internally. - There are a fixed number of threads in the pool
(this limit is not configurable). - Once exhausted, queued work items will have to
wait until a pool thread is released. - Offers easy access, but little control.
12Using the ThreadPool
- Call ThreadPool.QueueUserWorkItem() to submit a
task for background execution. - ThreadPool.RegisterWaitForSingleObject asks the
thread pool to watch for an event to become
signaled, and then call back on another pooled
thread. - Async delegates and Threading.Timer use the
thread pool internally.
13Async Delegates
- Compiler magic automatically adds a BeginInvoke
and EndInvoke signature to delegates that allow
them to be called using the thread pool. - Use BeginInvoke() to submit the delegate to the
pool. - (important) Every call to BeginInvoke needs to be
paired with EndInvoke to clean up resources.
14Async Delegates (cont.)
- BeginInvoke() returns an IAsyncResult reference
that is used later to call EndInvoke(). - An optional callback method can be passed to
BeginInvoke that will be executed by the
ThreadPool once the async call has completed.
15Async Delegates (example)
- Issuing multiple database queries concurrently
16Using Explicit Threads
- Explicit threads require a specific type of
delegate the ThreadStart delegate. - ThreadStart defines a simple method with no
parameters and no return value. - Instantiate a new Thread object using a
ThreadStart delegate. - Once created, a thread must be Started, and once
the delegate has ended, it cannot be re-used. It
must be destroyed.
17So how do we get data in or out?
- The Thread class itself is sealed ?
- Must create your own class to manage the threads
lifetime, and add your own specific data as
properties. - Assign the input properties before Starting.
- Use an endless loop with ResetEvents to control
iteration and loop exit point. - Signal an event when a task iteration is complete
to notify the calling code.
18DEMO 1
- A Custom Thread manager class to demonstrate
- Using events to control thread iteration and
completion - Using ThreadPool.RegisterWaitHandle()
- Using Thread/ThreadStart
- Performing UI Pump Thread Marshalling using
Control.Invoke()
19Explicit Threads (cont.)
- It is recommended that you not use Thread.Suspend
or Thread.Resume for flow control. In fact, these
methods are marked as Obsolete() in Framework
2.0. - Use ManualResetEvent, AutoResetEvent, and Monitor
for flow control. - It is recommended also that you not use
Thread.Abort(). This is a nasty way to interrupt
a thread. Use an event instead.
20Explicit Threads (cont.)
- Thread.IsBackground is used to mark a thread as
being non-critical. - Background threads will not prevent the CLR from
ending a process if they are still running during
shutdown. - Thread.Name can be used to give a descriptive
name that can be seen in the debuggers thread
list.
21Real-World Uses
- Windows Forms Applications To perform intensive
processing in the background while continuing to
interact with the user. - ASP.NET To perform multiple operations in
parallel instead of one at a time (multiple
database queries, etc). - The Async Design pattern can be incorporated into
Data Access Layer code (if you use code
generation techniques).
22Making It Work
- Synchronizing data access
- Use lock() / SyncLock to wrap complex data
manipulations into atomic units. - Lock() is a compiler macro the IL actually
contains calls to the Monitor class. - You can use the MethodImpl() attribute to cause
the CLR to effectively wrap a method call in
lock(this).
23Cautions for using lock()
- Do not use Value Types for the locking object.
They get boxed, causing the locks to be placed on
different objects each call. - Do not use lock() on any publicly accessible
object. This includes - lock(typeof(MyType))
- lock(some string)
- lock(MyForm.APublicProperty)
- lock(this)
- Public objects can be interfered with!
24Cautions for using lock() (cont.)
- You can use more than one locking object in a
class to improve concurrency. - A locking object should be used for each logical
data element you intend to protect. - Locking object example
25The Interlocked Class
- Allows quick, simple protection of basic data
manipulations. - Effectively wraps a simple common multi-step
operation in a lock() statement. - Interlocked.Increment()
- Interlocked.Decrement()
- Interlocked.CompareExchange()
- Interlocked.Exchange()
26The ReaderWriterLock Class
- Allows optimized locking for data that is read
often but written rarely. - Similar to Monitor, but allows any number of
readers concurrently. - Only allows a single Writer, which requires
exclusive access. - As with all locking mechanisms, acquire late and
release early!
27Event Classes
- ManualResetEvent / AutoResetEvent
- Have two states signaled and unsignaled
- Are used to communicate between threads within a
process. - AutoResetEvent is toggled to the Unsignaled state
automatically the ManualResetEvent remains
signaled until Reset() is called. - Mutex is very similar to ResetEvents, but can be
associated with a Name, and can be referenced in
other processes.
28Interacting With the UI Pump
- The main UI Pump Thread is the only thread
allowed to touch GDI objects. - Always use Control.Invoke() to pass execution to
the UI thread from a secondary thread. - Can use Control.InvokeRequired to determine if
you are already in the UI Thread. - Beware Invoke runs inside the message pump
thereby blocking it so its SLOW!
29Timers
- Timers are used to fire a repeating event on a
regular interval. - There are three timer classes one in
System.Windows.Forms another in System.Threading,
and the third in System.Timers.
30System.Windows.Forms.Timer
- Uses the main UI Pump.
- Inserts a message into the UI stream to process
the recurring event. - Timer event delegate is always called on the main
UI Thread. - Is not very accurate, since it depends on the
main UI thread to initiate events, which might be
busy with other tasks. - Iterations can be skipped if UI is busy.
31System.Threading.Timer
- Uses a background thread to spawn worker threads
in the thread pool for each iteration. - Is more accurate but must use Control.Invoke() if
it eventually interacts with the UI. - Has a more cumbersome interface than the other
timers, but more flexibility.
32System.Timers.Timer
- Uses a background thread to spawn worker threads
in the thread pool for each iteration. - If the SynchronizingObject property is assigned
to a UI Control, callbacks will automatically be
marshaled to the UI Thread. - If the UI Thread is busy, triggered events will
queue up until processed.
33Conversing Between Threads
- Dont use Suspend/Resume to control threads.
- Instead, use Events and Mutexes to coordinate
them. - When using an explicit thread, use a while loop
that watched for an Event to exit. This is more
efficient that creating a new thread for every
iteration.
34Race Conditions
- Occurs when two threads contend for the same data
in an unsynchronized way. - Causes logical data corruption or inconsistency
of data. - Solution Use Interlocked to protect simple
operations, and Monitor (or ReaderWriterLock) to
protect more complex interactions.
35Deadlocks
- Occurs when two threads are waiting on a resource
exclusively held by the other. - Solution Ensure that resources are accessed in
the same order always. - Be careful when executing threads on the
threadpool try to not create even more
threadpool threads (BeginXxx on a delegate for
example) and then block on them this will
exhaust the pool, effectively deadlocking it.
36New For Framework 2.0
- BackgroundWorker Class
- Uses an event-based mechanism to process
background tasks. - Handles main UI Thread synchronization
automagically. - Supports Progress Reporting / Completion
Notification Events. - Supports Cancellation of Worker Task
37The BackgroundWorker Class
- Uses a pooled worker thread to process the
background task, and then executes a completion
callback on the main UI thread. - This async patterns is also baked in to the
ADO.NET 2.0 and Web Service Proxy classes. - This is all possible in todays framework,
however you need to build your own worker classes
or set up the delegates to marshal calls back
into the UI Thread.
38Resources
- MSDN Library http//msdn.microsoft.com/library/
- Newsgroups
- http//groups.google.com/ or http//www.msdn.micr
osoft.com/newsgroups/