Microsoft.NET Architecture and the C# Language A semester course for 4 th year students Comments to the presentations Prof. Vladimir O. Safonov, St. Petersburg University Email: v_o_safonov@mail.ru WWW: http://user.rol.ru/~vsafonov Lecture 13. Slide 2. Multithreading and synchronization in .NET is the main subject of this lecture. Also, debugging and tracing features will be considered. Slide 3. Thread is a parallel branch of execution, or control flow, of an application that should perform a logically independent part of the application's task. The history of parallel processing starts with E. Dijkstra's paper on synchronization published in 1966. One of the first operating systems that supports parallel processing was UNIX whose first version was shipped in 1970. However, in UNIX a process is heavyweight in the sense that each process has its separate virtual address space, which is too resource comsuming. Lightweight process is a new kind of process first appeared in Windows NT and Solaris. It shares the same virtual address space with its parent process.To create a lightweight process, it is enough just to create a new stack for its execution. So, a thread can be considered a user level unit of parallel execution, implemented by a lightweight process or set of lightweight processes. Threading libraries in different operating systems have different APIs. So, an important task was to provide a hardware platform independent layer of threading for the users. Before Java and .NET neither of programming languages or platforms (e.g. C or C++) provided a unified threading model "at the application layer". Slide 4. Threads are a basis for solving tasks "in parallel style". The main advantages of using threads are as follows. They allow: - to avoid blocking the GUI by delegating to a separate thread processing GUI events; - to manage priorities of subtasks whose solutions were delegated to separate threads; - to organize potentially long-term tasks, like communicating to network or a DBMS server. The shortcoming of threads is related to inevitable overhead to context switching from one thread to another. Another one is making the programming model, design and implementation more complicated, taking into account the parallelism, as compared to traditional "sequential programming style". Slide 5. On .NET platform, threading support APIs are located in the System.Threading namespace. To decrease the complexity of design multi-threaded applications, .NET provides a set of patterns for design and asynchronous programming. They help to hide the fact of using threads and support delegates for callback methods used in asynchronous schemes. Slide 6. The architecture of a .NET process is shown on the scheme. Actually, a process is a running application. A process can contain one or more threads. There can be shared data used by several threads, and in addition each thread can have its thread-specific data. Slide 7. Another very important, absolutely new concept of .NET is application domain already considered before. Application domains are mini-processes in CLR. They enable application isolation for protecting against faults, designating the borders of type visibility and making security checks. Slide 8. The CLR treats executable applications as application domains. The role of application domains in CLR is similar to the role of processes in an operating system. One process can contain many application domains but each application domain can only belong to one process. Managing domains is much cheaper than managing processes. Switching an operating system thread between application domains is much faster than between processes. Slide 9. Properties of application domain are similar to environment variables. They can be read and updated by pairs of GetData and SetData methods whose first argument is the name of the property. The set of application domain properties is extensible. They are accessible either from inside or from outside the domain. Domain properties are used when searching assemblies. The GetAssemblies method returns an array of references to all assemblies loaded into an application domain. The current application domain is determined by the CurrentDomain static property of the AppDomain class. Slide 10. An application domain is a scope of types, static variables and objects. All of them belong to some definite application domain and are unloaded together with their host domain. Slide 11. Application domains protect users against faults inside the operating system's process. Each AppDomain has its own filter of unhandled exceptions, provided by the UnhandledExceptionsEvent and the related delegate. Each AppDomain can also define its own security policy level to enable a "top-of-stack" set of permissions for the methods to be executed within the application domain. This can be done via the SetAppDomainPolicy method. In addition, application domains are independently compiled and debugged. The borders of an application domain are closed for objects, except for passing them via the remoting mechanism. Slide 12. Here are the basic methods of domain programming API, provided by the AppDomain class: CreateDomain - create a new application domain; Unload - unload an application domain and release all its resources; Load - load a given assembly into a given domain; ExecuteAssembly - execute in a given domain the assembly contained in a specified file; CreateInstance - create a new instance of a specified type in a given assembly; DoCallback - call a given delegate in a given assembly. Slide 13. This example demonstrates how to use the CreateDomain and ExecuteAssembly methods to make child domains and execute assemblies. In the call of CreateDomain, the trailing null arguments stand for the security policy and the for domain initialization object. They can be omitted and one argument only, the name of the new domain, can be passed. In the call of ExecuteAssembly, the trailing null arguments stand for the supplied evidence for the assembly and for the array of arguments to the assembly entry point. They can be omitted and one argument only, the name of the assembly file, can be passed. Slide 14. To pass objects or their references across application domain borders, marshaling is required. There are two kinds of marshaling - marshal by reference and marshal by value. The former means that the object remains within the limits of its host domain, its state is not copied, and a proxy object on the client is created to get access to the object. The latter means that the state of the object is copied and actually the contents of the object is passed to the client in some standard serialized form. Anyway, the class of the object should be marked as Serialized, otherwise the object cannot be marshaled at all. The RemotingServices.Marshal method is used to create an ObjRef - a serializable representation of the object. It should be parsed to create a proxy for the object. To marshal an object by reference, the type of the object should be a descendant of MarshalByRefObject. Otherwise the object is marshaled by value. Slide 15. Now let's start with threading in .NET. As in Java, a new thread is initiated by creating a Thread object, and started to execute by the Start method, as shown on the example. But, unlike Java, to actually start execution of a thread, the method to be executed on the thread is defined by a special delegate whose type is indicated as ThreadStart. Slide 16. A thread has a name, priority and a flag to indicate whether it is a foreground thread or a background thread. All the three items are properties of the thread. Name is used to refer to the thread, priority is used to manage the order of executing threads. A thread is background if it does not prevent the host process from terminating. In Java, a similar concept is daemon thread. Slide 17. Managing threads in .NET is made very similar to Java. The static Sleep method blocks a thread for a given number of milliseconds. The Suspend and Resume methods serve to delay executing of the thread, and resume it again. It should be specially noted that, no matter these methods are presented to the users, they are potentially very dangerous because their inappopriate use can lead to deadlocks. For example, if a thread is suspended within its critical section, after locking some common resource but before unlocking it, this may cause a deadlock because other threads can permanently wait for this resource to get unlocked. For this reason, meanwhile, the authors of Java had to claim these methods to be deprecated. The Join method should be used to safely wait for another thread to terminate. So, the pair of Start and Join methods can be regarded as an analogy to the classical pair of fork and join methods in UNIX. The WaitHandle.WaitAny and WaitHandle.WaitAll methods are used for some events to occur. The events are related to availability of common shared resources. WaitAny waits for any of the specified WaitHandle array elements to receive a signal that the event has occurred (and return the index of the corresponding array element), WaitAll waits for all the elements to receive a signal. These methods support event synchronization paradigm for .NET threads. Slide 18. Thread static storage (or data) mechanism is necessary to support thread-specific information to be retrieved and updated. The ThreadStatic attribute marks a field to be unique for each thread. Addressing the field is made as usual, taking into account it is quite safe, since the field is unique for each thread. Similar features are supported in various operating systems like latest Windows or Solaris, and also in Java. Slide 19. Interrupting and terminating threads is done similar to Java, by generating special kinds of exceptions within the thread to be interrupted or terminated. The Interrupt method awakens a given thread and generates the ThreadInterruptedException in it. The awakened thread should catch and process this exception. Such situation can occur, for example, after calling Sleep. So, as well as in Java, Interrupt means to interrupt the state of sleeping, rather than to interrupt the thread itself. The Abort method attempts to abort a given thread. But, instead of actual abortion, a ThreadAbortException is generated in the thread to be aborted. The following depends on the way the thread processes the exception. This specific kind of exception is re-thrown at the end of each try-catch block, unless the thread calls the ResetAbort method that clears the state of automatic rethrowing. All in all, such way of thread abortion does not fully guarantee the termination of the thread. To guarantee that, the Join method should be invoked on that thread. Slide 20. The classical problems of race condition and threads synchronization arise for .NET threads as well as for any other kinds of parallel processes. The origins of these problems are very well described in the classical and pioneering paper by Edsger W. Dijkstra "Cooperating sequential processes" in 1966. Race condition is non-controlled order of access to shared resources. Deadlock is a chain of suspended threads such that each of them but the last is waiting for the next thread in the chain, and the last one is waiting for the first one. .NET offers a number strategies how to avoid race condition and deadlocks: - having a thread local copy of the sata; - synchronized context; - synchronized block of code; - manual synchronization. All of them will be considered in the next lecture. . course for 4 th year students Comments to the presentations Prof. Vladimir O. Safonov, St. Petersburg University Email: v_o_safonov@mail.ru WWW: http://user.rol.ru/~vsafonov Lecture 13. Slide. manage priorities of subtasks whose solutions were delegated to separate threads; - to organize potentially long-term tasks, like communicating to network or a DBMS server. The shortcoming of. specified type in a given assembly; DoCallback - call a given delegate in a given assembly. Slide 13. This example demonstrates how to use the CreateDomain and ExecuteAssembly methods to make child