23.6. Process Management

Traditionally, the unit of isolation for applications has been at the process level. A dedicated operating system user process is allocated to every instance of the running application, and stopping the application means killing the OS process. The .NET Framework introduces the concept of application domains, which are new, lighter units of isolation. The CLR allows a process to consist of many application domains, each independent of the others. Application domains thus create a boundary for applications to coexist within the same OS process. Hence, an interapplication method call is made within the same OS process, and that is lighter than making an interprocess method call. Applications can be stopped independently of each other, and a fault in one application does not affect the other applications in the same OS process.

The .NET application domain concept is reminiscent of Web-application isolation provided by Java-based application servers. The J2EE specification mandates that Web applications be independent of each other even though they are created in the same JVM instance. Classes loaded in one Web application are not available to another Web application. This isolation is achieved by having different class loaders for each Web application.

Associated with each application is an application domain. When you start, say, a .NET shell executable from the command line, a piece of software creates the default application domain and your application-specific domain. This piece of software is the CLR host. The CLR host is responsible for loading the CLR into the OS process, creating a logical partition within the OS process so that your application can reside in that partition. Running a .NET shell executable automatically invokes the CLR host associated with shell executables. In .NET an application domain is modeled using the System.AppDomain class.

23.6.1. Querying the Current Application Domain

Listing 23.12 shows how a shell executable can query its current application domain and get at its properties.

Listing 23.12. Current Application Domain Properties (C#)
using System;
using System.Threading;
public class Test {

  public static void Main(string[] args) {

    AppDomain domain = Thread.GetDomain();
    Console.WriteLine("Friendly name "+domain.FriendlyName);
    Console.WriteLine("Base directory "+domain.BaseDirectory);

    AppDomainSetup setup = domain.SetupInformation;
    Console.WriteLine("ApplicationBase "+setup.
                        ApplicationBase);
    Console.WriteLine("ApplicationName "+setup.
                        ApplicationName);
    Console.WriteLine("Config file "+setup.ConfigurationFile);
  }
}

The output of Listing 23.12 is as follows:

Friendly name Deployment.exe
Base directory C:BOOKcodeChapter 23DeploymentinDebug
ApplicationBase C:BOOKcodeChapter 23DeploymentinDebug
ApplicationName
Config file C:BOOKcodeChapter
23DeploymentinDebugDeployment.exe.config

You can obtain the application domain of the application by calling the GetDomain method of the current thread. The AppDomain class defines several properties of interest.

23.6.2. Executing an Application in a Remote Application Domain

To execute an application in another application domain, you must first create that application domain. Listing 23.13 shows how.

Listing 23.13. Executing an Assembly inside a Custom Application Domain (C#)
using System;

public class Test {

  public static void Main(string[] args) {

    AppDomainSetup info = new AppDomainSetup();
    info.ApplicationBase = System.Environment.CurrentDirectory;
    AppDomain dom = AppDomain.CreateDomain(
           "RemoteDomain", null,info);
    dom.ExecuteAssembly("c:\book\code\Chapter 23\MyApp.exe");
    AppDomain.Unload(dom);
  }
}

The output of Listing 23.13 is as follows:

A slightly different Hello World

Listing 23.13 creates a custom application domain called RemoteDomain and executes and runs a previously developed assembly (MyApp.exe). Note that in this case the application that is run is trivial, but in practice the application can be complex, and it is important to unload the assembly. There is no API for unloading an assembly directly, but you can do so by unloading the application domain in which it runs:

AppDomain.Unload(dom);

23.6.3. Invoking a Method in a Remote Application Domain

As mentioned earlier, in the absence of application domains, invoking methods of other applications typically means making an interprocess call, which is slower than making an intraprocess call. To demonstrate how to call a method of a class loaded in another application domain, we first create a simple class and create a library type assembly (DLL) out of it. Listing 23.14 shows the RemoteHello class that we will use to create the DLL.

Listing 23.14. RemoteHello.cs (C#)
using System;

public class RemoteHello : MarshalByRefObject {

  string greeting;
  public RemoteHello(string greeting) {
    this.greeting = greeting;
  }

  public void Greet() {
    Console.WriteLine("Hello "+greeting);
  }
}

MarshalByRefObject is an abstract class that enables access to objects across application domain boundaries.

Next, we compile RemoteHello.cs into RemoteHello.dll:

csc /target:library /out:RemoteHello.dll RemoteHello.cs

Now that we have the DLL, Listing 23.15 shows how to call the Greet() method by loading the DLL in a custom application domain.

Listing 23.15. RemoteHelloDriver.cs (C#)
using System;
using System.Reflection;
using System.Runtime.Remoting;

public class Test {

  public static void Main(string[] args) {

    //Set up information regarding the application domain
    AppDomainSetup info = new AppDomainSetup();
    info.ApplicationBase = System.Environment.CurrentDirectory;

    // Create an application domain with null evidence
    AppDomain dom = AppDomain.CreateDomain(
           "RemoteDomain", null,info);
    BindingFlags flags = (BindingFlags.Public |
      BindingFlags.Instance | BindingFlags.CreateInstance);
    ObjectHandle objh = dom.CreateInstance("RemoteHello",
      "RemoteHello", false, flags, null, new String[]{"Hello
      World!"}, null, null, null);
    Object obj = objh.Unwrap();
    // Cast to the actual type

    RemoteHello h = (RemoteHello)obj;
    // Invoke the method

    h.Greet();

    // Clean up by unloading the application domain
    AppDomain.Unload(dom);
  }
}

Because Listing 23.15 references the RemoteHello class, we must compile it using the following command line:

csc /r:RemoteHello.dll RemoteHelloDriver.cs

If we now run the RemoteHelloDrive.exe we will see the following output:

Hello Hello World!

Being able to execute other applications or even call methods on types defined in other applications without slowing execution is a huge positive of the .NET Framework. It allows enterprise developers to develop and deploy common enterprise components in one assembly and reuse those components in other applications.

We mentioned earlier that multiple applications can be run in a single OS process using the application domain as the unit of isolation and that you can stop one application without affecting the other. To illustrate this, we create Application1.cs (Listing 23.16) and Application2.cs (Listing 23.17) and then create the assemblies Application1.exe and Application2.exe, respectively, from the two listings.

Listing 23.16. Application1.cs (C#)
using System;
using System.Threading;

public class Test {

  static void Main(string[] args) {
    Thread t = new Thread(new ThreadStart(new Test().Run));
    t.Start();
  }

  public void Run() {
    while (true) {
      Thread.Sleep(400);
      Console.WriteLine("Running App 1");
    }
  }
}

Run the following command to create the Application1.exe assembly:

csc Application1.cs

Next, we create Application2.cs (Listing 23.17).

Listing 23.17. Application2.cs (C#)
using System;
using System.Threading;

public class Test{

  static void Main(string[] args) {
    Thread t = new Thread(new ThreadStart(new Test().Run));
    t.Start();
  }

  public void Run() {
    while (true) {
      Thread.Sleep(500);
      Console.WriteLine("Running App 2");
    }
  }
}

Run the following command to create the Application2.exe assembly:

csc Application2.cs

Now we create the source file that we will use to create two application domains and run Application1.exe and Application2.exe in these two domains. We will call this class Monitor.cs (Listing 23.18).

Listing 23.18. Monitor.cs (C#)
using System;
using System.Threading;
using System.Reflection;

public class Test {

  AppDomain dom1, dom2;

  public Test() {

    AppDomainSetup info = new AppDomainSetup();
    info.ApplicationBase = System.Environment.CurrentDirectory;

    dom1 = AppDomain.CreateDomain("RemoteDomain1", null, info);
    dom2 = AppDomain.CreateDomain("RemoteDomain2", null, info);
    dom1.ExecuteAssembly("c:\book\code\Chapter23
          \Deployment\Application1.exe");
    dom2.ExecuteAssembly("c:\book\code\Chapter23
          \Deployment\Application2.exe");

    public static void Main(string[] args) {
    Thread t = new Thread(new ThreadStart(new Test().Run));
    t.Start();
  }

    public void Run() {
      Thread.Sleep(1000);
      Console.WriteLine("Unloading dom1");
      AppDomain.Unload(dom1);
      Thread.Sleep(1000);
      Console.WriteLine("Unloading dom2");
      AppDomain.Unload(dom2);
        }
}

When we run Monitor.exe we get the following output:

Running App 1
Running App 2
Running App 1
Unloading dom1
Running App 2
Running App 2
Running App 2
Unloading dom2

The contents of Listing 23.18 are self-explanatory. The class instantiates a thread, and in its constructor it creates two application domains and executes Application1.exe and Application2.exe in those domains. Next, the instantiated thread sleeps for 1,000 ms and then unloads the application domain running Application1.exe. Note that after this statement, Application2.exe is the only assembly running and printing. The main thread then sleeps some more and then unloads the second application domain, thereby stopping all the applications.

System.Diagnostics

The System.Diagnostics namespace contains many useful classes that help your application code deal with the operating system. Although there are several fun Windows-specific operations that you can do using the classes in this namespace, one of the more important things, perhaps more familiar to Java programmers, is monitoring the operating system process. In Java, this is modeled using the java.lang.Process class. This class provides only read-only methods. You cannot create a process in Java using the methods of this class. In .NET the Process class of this namespace can be used to query existing process details as well as create new processes.

23.6.4. Querying Processes

Listing 23.19 shows how to query an existing process. The application repeatedly prints the details of the current process in which it runs. Because the application creates a thread, it keeps the process running, and hence you will get an unending stream of output until you kill the process.

Listing 23.19. Getting the Current .NET Process Details (C#)
using System;
using System.Threading;
using System.Diagnostics;

public class OSProcessApp {

  Process p;

  public OSProcessApp() { p = Process.GetCurrentProcess(); }

    static void Main(string[] args) {
      Thread t = new Thread(new ThreadStart(new
      OSProcessApp().Run));
      t.Start();
  }

  public void Run() {
    while (true) {
      Thread.Sleep(400);
      Console.WriteLine(p.BasePriority);
      Console.WriteLine(p.Id);
      Console.WriteLine(p.TotalProcessorTime);
      Console.WriteLine(p.UserProcessorTime);
      Console.WriteLine(p.VirtualMemorySize);
      Console.WriteLine(p.MachineName);
    }
  }
}

The output of Listing 23.19 will depend on your machine, but here is a sample excerpt:

Priority 8
Id 1380
Total processor time 00:00:00.5007200
User processor time 00:00:00.2303312
Virtual memory size 90882048
Machine name .
Priority 8
Id 1380
Total processor time 00:00:00.5007200
User processor time 00:00:00.2303312
Virtual memory size 90882048

23.6.5. Creating and Killing Processes

Listing 23.20 shows how to create a new process and then kill it. We create and kill the process required to run Application1.exe, created earlier in this chapter.

Listing 23.20. Creating and Killing Processes (C#)
using System;
using System.Threading;
using System.Diagnostics;

public class Test {

  static void Main(string[] args) {

    try {
      Process p = Process.Start("C:\BOOK\code\Chapter
      23\Deployment\Application1.exe");
      Thread.Sleep(5000);
      p.Kill();
    } catch (Exception e) {
      Console.WriteLine(e.StackTrace);
      }

  }
}

When you run Listing 23.20 from the command line, a second console window pops up and prints the following:

Running App 1
Running App 1
Running App 1
Running App 1
Running App 1
Running App 1

After about 5 seconds the second pop-up console window automatically closes itself.

Listing 23.20 uses the static method of the Process class to create a process. You can use the constructor instead and specify the ProcessStartInfo parameters corresponding to this process.

23.6.6. Redirecting Process Output

Sometimes it is necessary to redirect the output generated by a particular process. Java programmers attempting to get the process ID (PID) of a UNIX process are perhaps familiar with this approach of running a UNIX command and then parsing the output from the command. Listing 23.21 shows how to achieve that in C#.

Listing 23.21. Redirecting Output of a Process (C#)
using System;
using System.Diagnostics;

public class Test {

  public static void Main() {

    Process p = new Process();
    p.StartInfo.FileName = "cmd.exe";
    p.StartInfo.Arguments = "/c dir *.exe";
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.Start();
    string output = p.StandardOutput.ReadToEnd();
    Console.WriteLine("Output of command "+output);
  }

}

The output of Listing 23.21 is as follows:

Output of command Volume in drive C is Local Disk
Volume Serial Number is 50DD-3A83
Directory of C:BOOKcodeChapter 23DeploymentinDebug
01/05/2003 10:13p  5,632 Deployment.exe
1 File(s)  5,632 bytes
0 Dir(s) 14,532,866,048 bytes free

23.6.7. Detecting Process Completion

Earlier we created a process and then killed it without caring whether the process was finished executing. In practice, however, you want to know whether the process has completed. The .NET Framework provides an event handler hook that lets you know when the process has exited. Listing 23.22 takes the example in Listing 23.21 and adds event handling.

Listing 23.22. Detecting Process Completion (C#)
using System;
using System.Diagnostics;

public class Test {

  public static void Main() {

    Process p = new Process();
    p.StartInfo.FileName = "cmd.exe";
    p.StartInfo.Arguments = "/c dir *.exe";
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;

    p.EnableRaisingEvents = true;
    p.Exited += new EventHandler(ProcessDone);

    p.Start();

    string output = p.StandardOutput.ReadToEnd();
    Console.WriteLine("Output of command "+output);

    p.WaitForExit();
  }

  private static void ProcessDone(object sender, EventArgs e) {
    Console.WriteLine("Process Exited");
  }
}

The output of Listing 23.22 is as follows:

Volume Serial Number is 50DD-3A83
Directory of C:BOOKcodeChapter 23DeploymentinDebug
01/05/2003 10:13p  5,632 Deployment.exe
1 File(s) 5,632 bytes
0 Dir(s) 14,532,866,048 bytes free

23.6.8. Exiting a Process

Note that in Listing 23.22 we provide two ways of detecting process completion. One is by registering the ProcessDone event handler, and the other is the WaitForExit method, which returns when the process is finished.

The Process class provides several interesting methods and is richer in its API compared with its Java counterpart. For example, if you have been Web surfing a lot lately and want to quickly kill all your IE browser instances with one click, you can simply create an EXE assembly containing the code displayed in Listing 23.23.

Listing 23.23. Getting Rid of All IE Instances (C#)
using System;

using System.Diagnostics;
public class Test {
  public static void Main() {
    Process[] processes = Process.GetProcessesByName
("IEXPLORE");
    foreach (Process p in processes) {
      p.Kill();
    }
  }
}

C# also provides an API for querying services and operating system (Windows) log files. Classes for reporting at the level of the operating system are in the System.Diagnostics namespace.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.137.240