Thread Support in .NET and C

 < Day Day Up > 



Thread Support in .NET and C#

Free threading is supported in the .NET Framework and is therefore available in all .NET languages, including C# and VB.NET. In this next section, we will look at how that support is provided and more of how threading is done as opposed to what it is. We will also cover some of the additional support provided to help further separate processes

By the end of this section, you will understand:

  • What the System.AppDomain class is and what it can do for you

  • How the .NET runtime monitors threads

System.AppDomain

When we explained processes earlier in this chapter, we established that they are a physical isolation of the memory and resources needed to maintain themselves. We later mentioned that a process has at least one thread. When Microsoft designed the .NET Framework, it added one more layer of isolation called an application domain or AppDomain. This application domain is not a physical isolation as a process is; it is a further logical isolation within the process. Since more than one application domain can exist within a single process, we receive some major advantages. In general, it is impossible for standard processes to access each other's data without using a proxy. Using a proxy incurs major overheads and coding can be complex. However, with the introduction of the application domain concept, we can now launch several applications within the same process. The same isolation provided by a process is also available with the application domain. Threads can execute across application domains without the overhead associated with inter-process communication. Another benefit of these additional in-process boundaries is that they provide type checking of the data they contain.

Microsoft encapsulated all of the functionality for these application domains into a class called System.AppDomain. Microsoft .NET assemblies have a very tight relationship with these application domains. Any time that an assembly is loaded in an application, it is loaded into an AppDomain. Unless otherwise specified, the assembly is loaded into the calling code's AppDomain. Application domains also have a direct relationship with threads; they can hold one or many threads, just like a process. However, the difference is that an application domain may be created within the process and without a new thread. This relationship could be modeled as shown in Figure 9.

click to expand
Figure 9

start sidebar

In .NET, the AppDomain and Thread classes cannot be inherited for security reasons.

end sidebar

Each application contains one or more AppDomains. Each AppDomain can create and execute multiple threads. If you look at Figure 10, in Machine X there are two OS processes Y and Z running. The OS process Y has four running AppDomains: A, B, C, and D. The OS process Z has two AppDomains: A and B.

click to expand
Figure 10

Setting AppDomain Data

You've heard the theory and seen the models; now let's get our hands on some real code. In the example below, we will be using the AppDomain to set data, retrieve data, and identify the thread that the AppDomain is executing. Create a new class file called appdomain.cs and enter the following code:

    using System;    public class MyAppDomain    {      public AppDomain Domain;      public int ThreadId;      public void SetDomainData(string vName ,string vValue)      {        Domain.SetData(vName, (object)vValue);        ThreadId = AppDomain.GetCurrentThreadId();      }      public string GetDomainData(string name)      {        return (string)Domain.GetData(name);      }      public static void Main()      {        string DataName = "MyData";        string DataValue = "Some Data to be stored";        Console.WriteLine("Retrieving current domain");        MyAppDomain Obj = new MyAppDomain();        Obj.Domain = AppDomain.CurrentDomain;        Console.WriteLine("Setting domain data");        Obj.SetDomainData(DataName, DataValue);        Console.WriteLine("Getting domain data");        Console.WriteLine("The Data found for key '" + DataName                          + "' is '" + Obj.GetDomainData(DataName)                          + "' running on thread id: " + Obj.ThreadId);      }    } 

Your output should look something like this:

    Retrieving current domain    Setting domain data    Getting domain data    The Data found for key 'MyData' is 'Some Data to be stored' running on thread id: 1372 

This is straightforward for even unseasoned C# developers. However, let's look at the code and determine exactly what is happening here. This is the first important piece of this class:

        public void SetDomainData(string vName ,string vValue)        {          Domain.SetData(vName, (object)vValue);          ThreadId = AppDomain.GetCurrentThreadId();        } 

This method takes parameters for the name of the data to be set, and the value. You'll notice that the SetData () method has done something a little different when it passes the parameters in. Here we cast the string value to an Object data type as the SetData () method takes an object as its second parameter. Since we are only using a string, and a string inherits from System.Object, we could just use the variable without casting it to an object. However, other data that you might want to store would not be as easily handled as this. We have done this conversion as a simple reminder of this fact. In the last part of this method, you will notice that we can obtain the currently executing ThreadId with a simple call to the GetCurrentThreadId property of our AppDomain object.

Let's move on to the next method:

       public string GetDomainData(string name)       {         return (string)Domain.GetData(name);       } 

This method is very basic as well. We use the GetData () method of the AppDomain class to obtain data based on a key value. In this case, we are just passing the parameter from our GetDomainData() method to the GetData () method. We return the result of that method to the calling method.

Finally, let's look at the Main () method:

       public static void Main()       {         string DataName = "MyData";         string DataValue = "Some Data to be stored";         Console.WriteLine("Retrieving current domain");         MyAppDomain Obj = new MyAppDomain();         Obj.Domain = AppDomain.CurrentDomain;         Console.WriteLine("Setting domain data");         Obj.SetDomainData(DataName, DataValue);         Console.WriteLine("Getting domain data");         Console.WriteLine("The Data found for key '" + DataName                           + "' is '" + Obj.GetDomainData(DataName)                           + "' running on thread id: " + Obj.ThreadId);       } 

We start by initializing the name and value pairs we want to store in our AppDomain and writing a line to the console to indicate our method has started execution. Next, we set the Domain field of our class with a reference to the currently executing AppDomain object (the one in which your Main () method is executing). Next we call our methods - passing both parameters to the SetDomainData() method:

    Obj.SetDomainData(DataName, DataValue); 

Moving on, we pass one parameter into GetDomainData() method to get the data we just set and insert it into our console output stream. We also output the ThreadId property of our class to see what our executing ThreadId was in the method we called.

Executing Code within a Specified AppDomain

Now let's look at how to create a new application domain and make some important observations about the behavior when creating threads within the newly created AppDomain. The following code is contained within create_appdomains.cs:

    using System;    public class CreateAppDomains    {      public static void Main()      {        AppDomain DomainA;        DomainA = AppDomain.CreateDomain("MyDomainA");        string StringA = "DomainA Value";        DomainA.SetData("DomainKey", StringA);        CommonCallBack();        CrossAppDomainDelegate delegateA =            new CrossAppDomainDelegate(CommonCallBack);        DomainA.DoCallBack(delegateA);      }      public static void CommonCallBack()      {        AppDomain Domain;        Domain = AppDomain.CurrentDomain;        Console.WriteLine("The Value '" + Domain.GetData("DomainKey") +            "' was found in " + Domain.FriendlyName.ToString() +            " running on thread id: " +            AppDomain.GetCurrentThreadId().ToString());      }    } 

The output of this compiled class should look similar to this:

    The Value " was found in create_appdomains.exe running on thread id: 1372    The Value 'DomainA Value' was found in MyDomainA running on thread id: 1372 

You'll notice in this example we have created two application domains. To do this, we call the CreateDomain() static method of the AppDomain class. The parameter that the constructor takes is a friendly name for the AppDomain instance that we are creating. We will see that we can access the friendly name later by way of a read-only property. Here is the code that creates the AppDomain instance:

         AppDomain DomainA;         DomainA = AppDomain.CreateDomain("MyDomainA"); 

Next we call the SetData () method that we saw in the previous example. We won't redisplay the code here because we explained its use earlier. However, what we need to explain next is how we get code to execute in a given AppDomain. We do this with the DoCallBack() method of the AppDomain class. This method takes a CrossAppDomainDelegate as its parameter. In this case, we have created an instance of a CrossAppDomainDelegate passing the name of the method we wish to execute into the constructor:

         CommonCallBack();         CrossAppDomainDelegate delegateA =             new CrossAppDomainDelegate(CommonCallBack);         DomainA.DoCallBack(delegateA); 

You'll notice that we call CommonCallBack() first. This is to execute our CommonCallBack () method within the context of the main AppDomain. You'll also notice from the output that the FriendlyName property of the main AppDomain is the executable's name.

Lastly, let's look at the CommonCallBack() method itself:

         public static void CommonCallBack()         {           AppDomain Domain;           Domain = AppDomain.CurrentDomain;           Console.WriteLine("The Value '" + Domain.GetData("DomainKey") +               "' was found in " + Domain.FriendlyName.ToString() +               " running on thread id: " +               AppDomain.GetCurrentThreadId().ToString());         } 

You'll notice that this is rather generic so it will work in no matter what instance we run it. We use the CurrentDomain property once again to obtain a reference to the domain that is executing the code. Then we use the FriendlyName property again to identify the AppDomain we are using.

Lastly, we call the GetCurrentThreadId() method again here. When you look at the output, you can see that we get the same thread ID no matter what AppDomain we are executing in. This is important to note because this not only means that an AppDomain can have zero or many threads, but also that a thread can execute across different domains.

Thread Management and the .NET Runtime

The .NET Framework provides more than just the ability for free-threaded processes and logical application domains. In fact, the .NET Framework supplies an object representation of processor threads. These object representations are instances of the System.Threading.Thread class. We will go into this in more depth in the next chapter. However, before we move on to the next chapter, we must understand how unmanaged threads work in relation to managed threads. That is to say, how unmanaged threads (threads created outside of the .NET world) relate to instances of the managed Thread class, which represent threads running inside the .NET CLR.

The .NET runtime monitors all threads that are created by .NET code. It also monitors all unmanaged threads that may execute managed code. Since managed code can be exposed by COM-callable wrappers, it is possible for unmanaged threads to wander into the .NET runtime.

When unmanaged code does execute in a managed thread, the runtime will check the TLS for the existence of a managed Thread object. If a managed thread is found, the runtime will use that thread. If a managed thread isn't found, it will create one and use it. It's very simple, but is necessary to note. We would still want to get an object representation of our thread no matter where it came from. If the runtime didn't manage and create the threads for these types of inbound calls, we wouldn't be able to identify the thread, or even control it, within the managed environment.

The last important note to make about thread management is that once an unmanaged call returns back to unmanaged code, the thread is no longer monitored by the runtime.



 < Day Day Up > 



C# Threading Handbook
C# Threading Handbook
ISBN: 1861008295
EAN: 2147483647
Year: 2003
Pages: 74

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net