Threading has always been an important topic with COM objects.COM defines apartmerlt models for . synchronization. With a single-threaded apartment (STA),the COM runtime does the synchronization. A multithreaded apartment (MTA)means better performance but without synchronization by the COM runtime.
A COM component defines the apartment model it requires by setting a configuration value in the registry. ACOM component that is developed in a thread-safe manner supports the MTA. Multiple threads can access this component at once, and the component must do synchronization on its own.
ACOM component that doesn’t deal with multiple threads requires an STA.Here, just one (and always the same) thread accesses the component. Another thread can access the component only by using a
proxy that sends a Wmdows message to the thread that is connected to the COM object.STAsuse Windows messages for synchronization.
Visual Basic6 components supported only the STAmodel. ACOM component that is configured with the option both supports both STAand MTA.
Whereas the COM component defines the requirements for the apartment, the thread that instantiates the COM object defines the apartment it is running in. This apartment should be the same one that the ,.’Vivi component requires.
A .NET thread, by default, runs in a MTA.Youhave probably already seen the attribute [STAThread] with the Main () method of a Wmdows application. This attribute specifies that the main thread joins an STA.Windows Forms applications require an STAthread.
When creating a new thread, you can define the apartment model either by applying the attribute [STAThread] or [MTAThread] to the entry point method of the thread or by invoking the SetApartmentState () method of the Thread class before starting the thread:
Thread tl = new Thread(DoSorneWork);
tl. Start () ;
Youcan get the apartment of the thread with the GetApartmentThread () method
In Chapter 24, “Inieroperabilitv,” you can read about .NET interop with COM components and mort about CQ.Mapartment models.
Event-Based Asynchronous Pattern
Earlier in this chapter, you saw the asynchronous pattern based on the IAayncR •• ul t interface. With an asynchronous callback, the callback thread is different from the calling thread. Using Wmdows Forms or
WPF,this is a problem, because Wmdows Forms and WPF controls are bound to a single thread. With every control, you can invoke methods only from the thread that created the control, This also means
that if you have a background thread, you cannot directly access the UI controls from this thread. The only methods with Wmdows Forms controls that you can invoke from a diHerept thread than the creator thread are Invoke ( ), BeginInvoke ( ), End Invoke ( ” and the property InvokeRequired. BeginInvoke () and Endlnvoke () are asynchronous variants of Invoke ( ) . These methods switch to the creator thread to invoke the method that is assigned to a delegate parameter that you can pass to these methods. Using these methods is not hat easy, which is why, since .NET 2.0, a new component together with a new asynchronous pattern wa invented: the event-based asynchronous pattern. With the event-based asynchronous pattern, t e asynchronous component offers a method with the suffix Async; for example, the synchronous m”thod DoATask () has the name DoATaskAsyilc () in the asynchronous version. To get the result information, the component also needs to define an event that has the suffix Coinpleted, for example, DoATaskCompleted. While the action happening in the .
DoATaskAsync () method is running in a background thread, the event DoATaskCompleted is fired in the same thread as the caller.
With the event-based asynchronous pattern, the asynchronous component optionally can support cancellation and information about progress. For cancellation, the method should have the name CancelAsync ( ), and for pcogress information, an event with the suffix ProgressChanged, for example, DoATaskProgressChanged, is offered.
If you haven’t written any Windows applications until now, you can skip this section of the chapter and keep itfor. later. Just remember, using threads from Windows applications adds another complexity,
and you should come back here after reading the Windows Forms chapters (Chapters 31 to 33) or WPF chapters (Chapters 34 and 35). bi any case, the Windows Forms application demonstrated here is very simple from a Windows Forms viewpoint.
The BackgroundWorker class is one implementation of the asynchronous event pattern. This class implements methods, properties, and events, as described in the following table
Another class that implements the asynchronous event pattern is the component WebClient in the System. Net namespace. This class uses the WebRequest and WebResponse classes but offers an easier-to-use interface. The WebReques t and WebResponse classes also offer asynchronous programming, but here it is based on the asynchronous pattern with the IAsyncResul t interface.
The sample application demonstrates the use of the BackgroundWorker control in a Windows Forms application by doing a task that takes some time. Create a new Wmdows Forms application and add three Label controls, three TextBox controls, two Button controls, one ProgressBar, and one BackgroundWorker to the form, as shown in Figure 19-3.
Configure the properties of the controls as listed in the following table.
Add the struct CalcInput to the project. This struct will be used to contain the input data from the TextE\..ox controls.
The method OnCalculate () is the event handler for the Click event from the Button control named buttonCalculate. In the implementation buttonCalculate is disabled, so the user cannot click the button once more until the calculation is completed. To start the BackgroundWorker, invoke the method RunWorkerAsync ( ) . The BackgroundWorker uses a thread pool thread to do the calculation.
RunWorkerAsync () requires the input parameters that are passed to the handler that is assigned to the DoWork event.
The method OnDoWork () is connected to the DoWork event of the BackgroundWorker control. With the DoWorkEventArgs, the input parameters are received with the property Argument. The implementation simulates functionality that takes some time with a sleep time of 5 seconds. After sleeping, the result of the calculation is written to the Resul t property of DoEventArgs. If you add the calculation and sleep to the OnCalculate J) method instead, the Windows application is blocked from user input while this is active. However, here, a separate thread is used and the user interface is still active.
After OnDoWork is completed, the background worker fires the RunWorkerCompleted event. The method OnWorkCompleted ( ). is associated with this event. Here, the result is received from the Resul t
property of the RunWorkerCompletedEventArgs parameter, and this result is written to the result TextBox control. When firing the event, the BackgroundWorker control changes control to the creator thread, so there is no need to use the Invoke methods of the Wmdows Forms controls, and you can invoke properties and methods of Wmdows Forms controls directly.
Now you can test the application and see that the calculation runs independently of the UI thread, the UI is still active, and the Form can be moved around. However, the cancel and progress bar functionality
still needs implementation.
To enable the cancel functionality to stop the thread’s progress while it is running, you must set the BackgroundWorker property WorkerSupportsCancellation to True. Next, you have to implement
the OnCancel handler that is connected to the Click event of the control buttonCancel. The BackGroundWorker control has the CancelAsync () method to cancel an asynchronous task that is going on
The asynchronous task is not canceled automatically. In the OnDoWork () handler that does the asynchroru us task, you must change the implementation to examine the CancellationPending property of the BackgroundWorker control. This property is set as soon as CancelAsync () is invoked. If a cancellation is pending, set the Cancel property of DoWorkEventArgs to true and exit the handler.
The completion handler OnWorkCompleted () is invoked if the asynchronous method has completed successfully or if it was canceled. If it was canceled, you cannot access the Resul t property, because this throws an InvalidOperationException with the information that the operation has been canceled. So, you have to check the Cancelled property of RunWorkerCompletedEventArgs and behave
Running the application once more, you can cancel the asynchronous progress from the user interface.
To get progress information to the user interface, you must set the BackgroundWorker property WorkerReportsProgress to True. With the OnDoWorkmethod, you can report the progress to the BackgroundWorker control with the ReportProgress () method:
The method ReportProgress () fires the ProgressChanged event of the BackgroundWorker control. This event changes the control to the UI thread. Add the method OnProgressChanged () to the ProgressChanged event, and in the implementation set a new value to the progress bar control that is received from the property ProgressPercentage of ProgressChangedEventArgs:
In the OnWorkCompleted () event handler, the progress bar finally is set to the 100% value: private void OnWorkCompleted(object sender,
shows the running application while the calculation is just active.
Creating an Event-Based Asynchronous Component
To create a custom component that supports the event-based asynchronous pattern, more work needs to be done. To demonstrate this with a simple scenario, the class AsyncComponent just returns a converted input string after a time span, as you can see with the synchronous method LongTask ( ) . To offer asynchronous support, the public interface offers the asynchronous method LongTaskAsync () and the event LongTaskCompleted. This event is of type LongTaskCompletedEventHandler that defines the parameters
object sender and LongTaskCompletedEventArgs e. LongTaskCompletedEventArgs is a new type where the caller can read the result of the asynchronous operation . In addition, some helper methods such as DOL’ongTask and CompletionMethod are needed; these are discussed next
using system.ComponentModel; .
using System. Threading
The method L.ongTaskAsync needs to start the synchronous operation asynchronously. If the component allows starting the asynchronous task several times concurrently, the client needs to have an option to map the different results to the tasks started. This is why the second parameter of LongTaskAsync requires a taskld that can be used by the client to map the results. Of course, inside the component itself the task 10 needs to be remembered to map the results .. NET offers the class AsyncOperationManager to create AsyncOperationObjectsio help keep track of the state of operations. The class AsyncOperationManager has one method, CreateOperation, where a task identifier can be passed, and an AsyncOperation object is returned. This operation is kept as an item in the dictionary user st.at eni c t icnary that was created earlier. Then, a delegate o~type LongTaskWorkHandler is created, and the method DoLongTask is assigned to that delegate instance. Beginlnvoke () is the method of the delegate to start the method DoLongTask ( ) asynchronously using a thread from the thread pool.
LongTaskWorkHandler longTaskDelegate = DoLongTask;
longTaskDelegate. Beginlnvoke (input, asyncOp, null, null);
The delegate type LongTaskWorkHandler is just defined within the class AsyncComponent with a private access modifier because it is not needed outside. The parameters needed with this delegate are
all input parameters from the caller plus the AsyncOperation parameter for getting the status and mapping the result of the operation.
private delegate void LongTaskWorkHandler(string input,
The method DoLongTask () is now called asynchronously by using the delegate. The synchronous method LongTask () can now be invoked to get the output value.
Because an exception that might happen inside the synchronous method should not just blow up the background thread, any exception is caught and remembered with the variable e of type Exception. Finally, the CompletionMethod () is invoked to inform the caller about the result.
With the implementation of the CQmpletionMethod, the userStateDictionary is cleaned up as the operation is removed. The PostoPerationcompleted () method of the AsyncOperation object ends the lifetime of the asynchronous operation and informs the caller using the onCompletedDelegate method .•This method ensures that the delegate is invoked on the thread as needed for the application
type. To get information to the caller, an object of type LongTaskCompletedEventArgs is created and passed to the method PostOperationCompleted ( ) .
For passing information to the caller, the class LongTaskCompletedEventArgs derives from the base class AsyncCompletedEventArgs and adds a property containing output information. In the constructor, the base constructor is invoked to pass exception, cancellation, and user state information. public class LongTaskCompletedEventArgs : AsyncCompletedEventArgs
The method asyncOp. PostOperationCompleted () uses the onCompletedDelegate. This delegate was initialized to reference the method LongTaskCompletion. LongTaskCompletion needs to fulfill
the parameter requirements of the SendOrPostCallbackDelegate. The implementation just casts the paratfteter to LongTaskCompletedEventArgs, which was the type of the object that was passed to the PostOperationCompleted method, and calls the method OnLongTaskCompleted.
OnLongTaskCompleted then just fires the event LongTaskCompleted to return the LongTaskCompletedEventArgs to the caller.
After creating the component, it is really easy to use it. The event LongTaskCompleted is assigned to the method Comp_LongTaskCompleted, and the method LongTasKAsync () is invoked. With a simple
console application, you will see that the event handler Comp_LongTaskCompleted is called from a thread different from the main thread. (This is different from Windows Forms applications, as you will see next.)
With a Windows Forms application the SynchronizationContext is set to WindowsFormsSynchronizationContext – that’s why the event handler code is invoked in the same thread:
Some final guidelines regarding threading:
¤ Try to keep synchronization requirements to a minimum. Synchronization is complex and” blocks threads. You can avoid it if you try to avoid sharing state. Of course, this is not always possible.
¤ Static members of a class should be thread-safe. Usually, this is the case with classes in the .NE'( Framework.
¤ Instance state does not need to be thread-safe. For best performance, synchronization is better used outside of the class where it is needed and not with every member of the class. Instance members of .NET Framework classes usually are not thread-safe. In the MSDN library you can find this information documented for every class of the Framework in the Thread Safety section.
The next chapter gives information on another core .NET topic: security .