Chapter 42. Processes and Multithreading

In our everyday lives, we all do a number of things such as go to work, have appointments, and visit with friends or family; we are all very busy, of course. Sometimes we can do two things simultaneously, such as speaking on the phone while writing something on a piece of paper, but in most cases we do just one thing at a time. After all, there is only one of us. It would be great if we could share our things to do with other people so that multiple people could do the same work concurrently. We would be less tired and would have more time for relaxing or being with our families. In the computers’ world, a similar problem exists. You can compare a real person to an application. If an application has to complete hard and long work totally alone, it can cause overhead on the system and take more time. Moreover, recent hardware architectures (such as multicore processors) would remain unexploited. So it would be useful to have the ability to split the work of an application among multiple parts that could work concurrently. This is where threading comes in with the .NET Framework development. With threading, you can create multiple threads of work to perform multiple tasks concurrently so that your applications can take advantage of optimized performance and resources. But threading is not the only way you request actions. In many circumstances, you need to launch external executables and possibly hold a reference to them in your code, so you also often work with processes. In this chapter, you take a look at how the .NET Framework enables you to manage processes and how you can split operations across multiple threads—both created manually and provided by the .NET thread pool.

Managing Processes

You use the System.Diagnostics.Process class to manage processes on your machine.

This class offers both shared and instance members so that you can launch an external process but also get a reference to one or more processes. The following code shows how to launch an external process via the shared implementation of the Start method:

Process.Start("Notepad.exe")

Any call to the Process.Start method will return a Process object. You can also specify arguments for the process by specifying the second parameter for the method as follows:

Process.Start("Notepad.exe", "C:aFile.txt")

One of the most important features of the Start method is that you can also supply the username, password, and domain for launching a process:

Process.Start("Notepad.exe", "C:aFile.txt",
              "Alessandro", Password, "\MYDOMAIN")

Notice that the password is necessarily an instance of the System.Security.SecureString class, so see the MSDN documentation about this. The Process class also has an instance behavior that enables you to get a reference to a process instance. This is useful when you want to programmatically control a process. With regard to this, you first need an instance of the ProcessStartInfo class that can store process execution information. The class exposes lots of properties, but the most important are summarized in the following code snippet:

Dim procInfo As New ProcessStartInfo
With procInfo
    .FileName = "Notepad.exe"
    .Arguments = "aFile.txt"
    .WorkingDirectory = "C:"
    .WindowStyle = ProcessWindowStyle.Maximized
    .ErrorDialog = True
End With

Particularly, the ErrorDialog property makes the Process instance display an error dialog box if the process cannot be started regularly. When you have done this, you create an instance of the Process class and assign its StartInfo property; finally, you invoke Start as demonstrated in the following code:

Dim proc As New Process
proc.StartInfo = procInfo
proc.Start()

'Alternative syntax:
'Dim proc As Process = Process.Start(procInfo)

Approaching processes in this fashion is helpful if you need to programmatically control processes. For example, you can wait until a process exits for the specified number of milliseconds as follows:

'Waits for two seconds
proc.WaitForExit(2000)

To close a process, you write the following code:

proc.Close()

Finally, you can kill unresponsive processes by invoking the Kill method as follows:

proc.Kill()

The Process class also exposes the EnableRaisingEvents Boolean property, which enables you to set whether the runtime should raise the Exited event when the process terminates. Such an event is raised if either the process terminates normally or an invocation to the Kill method occurs. So far, you have learned how to launch processes, but the Process class is also useful when you need to get information on running processes, as discussed in the next subsection.

Querying Existing Processes

You can easily get information on running processes through some methods from the Process class that provide the ability of getting process instances. For example, GetProcesses returns an array of Process objects, each one representing a running process. GetProcessById and GetProcessByName return information on the specified process given the identification number or name, whereas GetCurrentProcess returns an instance of the Process class representing the current process. Then the Process class exposes a lot of useful properties for retrieving information. Each of them should be self-explanatory, such as ProcessName, Id, ExitCode, Handle, and HasExited. However, other advanced information properties are available, such as PageMemorySize or VirtualMemorySize, which return the memory size associated with the process on the page memory and the virtual memory, respectively. The Visual Studio’s Object Browser and IntelliSense can help you with the rest of the available properties. At the moment, let’s focus on how you can get information on running processes. The coolest way of getting process information is by using LINQ to Objects. The following query, and subsequent For..Each loop, demonstrates how to retrieve a list of names of running processes:

Dim processesList = (From p In Process.GetProcesses
                     Select p.ProcessName).AsEnumerable

For Each procName In processesList
    Console.WriteLine(procName)
Next

Notice that the query result is converted into IEnumerable(Of String) so that you can eventually bind the list to a user interface control supporting the type.

Introducing Multithreading

A thread is a unit of work. The logic of threading-based programming is performing multiple operations concurrently so that a big operation can be split across multiple threads. The .NET Framework 4.5 offers support for multithreading via the System.Threading namespace. But .NET 4.5 also takes from its predecessor an important library, which is discussed in Chapter 43, “Parallel Programming and Parallel LINQ.” It provides support for the parallel computing. For this reason, this chapter provides summary information on the multithreading approach so that in the next chapter you get more detailed information on the task-based programming. After reading Chapter 43, approaching the most important new language feature in Visual Basic 2012 will be easier. This is discussed in Chapter 44, “Asynchronous Programming.”


Imports Directives

The code examples shown in this chapter require an Imports System.Threading directive.


Creating Threads

You create a new thread for performing an operation with an instance of the System.Threading.Thread class. The constructor of this class requires you to also specify an instance of the System.Threading.ThreadStart delegate that points to a method that can actually do the work. Then you invoke the Thread.Start instance method. The following code snippet demonstrates how you can create a new thread:

Private Sub simpleThread()
    Dim newThread As New Thread(New ThreadStart(AddressOf _
                                                executeSimpleThread))
    newThread.Start()
End Sub

Private Sub executeSimpleThread()
    Console.WriteLine("Running a separate thread")
End Sub

To actually start the new thread, you invoke the method that encapsulates the thread instance, which in this case is simpleThread.

Creating Threads with Lambda Expressions

You might recall from Chapter 20, “Advanced Language Features,” that lambda expressions can be used anywhere you need a delegate. This is also true in threading-based programming. The following code snippet demonstrates how you can use statement lambdas instead of providing an explicit delegate:

Private Sub lambdaThread()
    Dim newThread As New Thread(New _
                     ThreadStart(Sub()
                                   Console.WriteLine("Thread with lambda")
                                 End Sub))
    newThread.Start()
End Sub

Now you can invoke the lambdaThread method to run a secondary thread, and with one method you reach the same objective of the previous code where two methods were implemented.

Passing Parameters

In many cases, you might need to pass data to new threads. You can do this by creating an instance of the ParameterizedThreadStart delegate, which requires an argument of type Object that you can use for sharing your data. The following code demonstrates how you create a thread with parameters:

Private Sub threadWithParameters(ByVal parameter As Object)
    Dim newThread As New Thread(New  _
                                ParameterizedThreadStart(AddressOf _
                                executeThreadWithParameters))
    newThread.Start(parameter)
End Sub

Notice how the Thread.Start method has an overload that takes the specified parameter as the data. Because such data is of type Object, you need to convert it into the most appropriate format. The following code demonstrates how to implement a method to which the delegate refers and how to convert the data into a hypothetical string:

Private Sub executeThreadWithParameters(ByVal anArgument As Object)
    Dim aString = CType(anArgument, String)
    Console.WriteLine(aString)
End Sub

You can use lambda expressions if you do not want to provide an explicit delegate in this kind of scenario.

Understanding the .NET Thread Pool

In the previous section, you saw how simple it is to create and run a new thread. When you have one or two threads, things are also easy for performance. But if you decide to split a process or an application across several concurrent threads, the previous approach can cause performance and resources overhead. So, you should manually search for the best configuration to fine-tune system resource consumption with your threads. Your application can run on different configurations in terms of available memory, processors, and general resources, so it is difficult to predict how many threads you can launch concurrently on target machines without affecting performance and causing overhead. Fortunately, the .NET Framework maintains its own set of threads that you can also reuse for your purposes instead of writing code for creating and running new threads, ensuring that only the specified number of threads will be executed concurrently—all controlled by the Framework. The set is named thread pool, and you access it via the System.Threading.ThreadPool class. This class offers static methods for assigning tasks to threads in the box because the thread pool has a predefined number of available threads. If they are all busy doing something else, the new task is put into a queue and is executed when a thread completes its work. To take advantage of threads in the thread pool, you invoke the System.Threading.ThreadPool.QueueUserWorkItem method, as demonstrated in the following code:

Sub QueueWork()

    ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf FirstWorkItem))
    ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SecondWorkItem))
    ThreadPool.QueueUserWorkItem(New WaitCallback(Sub()
                                                      Console.
                                                      WriteLine _
                                                      ("Third work item")
                                                  End Sub))

End Sub

Private Sub FirstWorkItem(ByVal state As Object)
    Console.WriteLine("First work item")
End Sub
Private Sub SecondWorkItem(ByVal state As Object)
    Console.WriteLine("Second work item")
End Sub

With QueueUserWorkItem you ask the runtime to put the specified task in the execution queue so that it will be executed when a thread in the thread pool is available. The WaitCallBack delegate allows passing state information and requires referred methods to have an argument of type Object in their signatures. Notice how you can still use lambdas to supply the desired action.


The Thread Pool and Windows Phone

Silverlight for Windows Phone also supports a special version of the thread pool that constitutes the simplest way to create responsive code by splitting tasks over threads. You use QueueUserWorkItem in Windows Phone to execute a task outside the user interface (UI) thread, thus preventing your app from freezing.


Getting and Setting Information in the Thread Pool

You can query information on the thread pool by invoking the ThreadPool.GetMaxThreads, ThreadPool.GetMinThreads, and ThreadPool.GetAvailableThreads methods. GetMaxThreads return the maximum number of concurrent threads that are held by the thread pool; GetMinThreads return the number of idle threads that are maintained waiting for the first new task being requested; and GetAvailableThreads returns the number of available threads. Whichever you use, they all return two values: the number of worker threads and the number of completion threads. Worker threads are units of execution, whereas completion threads are asynchronous I/O operations. The following code demonstrates how you get information on available threads:

Sub PoolInfo()
    Dim workerThreads As Integer
    Dim completionPortThreads As Integer

    ThreadPool.GetAvailableThreads(workerThreads,
                                   completionPortThreads)
    Console.WriteLine("Available threads: {0}, async I/O: {1}",
                      workerThreads, completionPortThreads)
    Console.ReadLine()
End Sub

The workerThreads and completionPortThreads arguments are passed by reference; this is why you need variables for storing values. Similarly, you can use SetMaxThreads and SetMinThreads to establish the maximum number of requests held by the thread pool and the minimum number of idle threads. The following line is an example:

ThreadPool.SetMaxThreads(2000, 1500)


Changing Default Values

You should take care when editing the default values for the thread pool. You should do it only when you have a deep knowledge of how many resources will be consumed on the machine and of the system resources so that edits will not be negative for the target system. Default values in the thread pool are high enough, but you can check this out by invoking GetMaxThreads.


Threads Synchronization

So far, you have learned how to create and run new threads of execution to split big operations across multiple threads. This is useful, but there is a problem: Imagine you have multiple threads accessing the same data source simultaneously—what happens to the data source, and how are threads handled to avoid errors? This is a problem that is solved with thread synchronization. The idea is that, when a thread accesses a resource, this resource is locked until the required operations are completed to prevent other threads from accessing that resource. Visual Basic and the .NET Framework provide keywords and objects, respectively, to accomplish threads synchronization, as covered in the next subsections.

The SyncLock..End SyncLock Statement

The Visual Basic language offers the SyncLock..End SyncLock statement, which is the place where you can grant access to the specified resource to only one thread per time. For example, imagine you have a class where you define a list of customers and a method for adding a new customer to the list, as demonstrated by the following code snippet:

Private customers As New List(Of String)

Sub AddCustomer(ByVal customerName As String)

    SyncLock Me
        customers.Add(customerName)
    End SyncLock
End Sub

The preceding code locks the entire enclosing class, preventing other threads from accessing the instance until the requested operation completes. Locking an entire class is not always the best idea because it can be expensive in terms of resources and performance and other threads cannot also access other members. Unfortunately, you cannot directly lock the resource; the MSDN documentation in fact states that you need to declare a lock object that you can use as follows:

Private customers As New List(Of String)
Private lockObject As New Object()

Sub AddCustomer(ByVal customerName As String)

    SyncLock lockObject
        customers.Add(customerName)
    End SyncLock
End Sub

The lock object is typically a System.Object. Using an object like this can ensure that the code block executed within SyncLock..End SyncLock will not be accessible by other threads. Another approach is using GetType instead of the lock object, pointing to the current type where the synchronization lock is defined. The following code demonstrates this:

Class Customers
    Inherits List(Of String)

    Public Sub AddCustomer(ByVal customerName As String)
        SyncLock GetType(Customers)
            Me.Add(customerName)
        End SyncLock
    End Sub
End Class

The SyncLock..End SyncLock statement is typical of Visual Basic language grammar. By the way, the statement is translated behind the scenes into invocations to the System.Threading.Monitor class, as described in the next section.

Synchronization with the Monitor Class

The System.Threading.Monitor class is the support object for the SyncLock..End SyncLock statement, and the compiler translates SyncLock blocks into invocations to the Monitor class. You use it as follows:

Sub AddCustomer(ByVal customerName As String)
    Dim result As Boolean

    Try
        Monitor.Enter(lockObject, result)
        customers.Add(customerName)
    Catch ex As Exception
    Finally
        Monitor.Exit(lockObject)
    End Try
End Sub


Tip

Monitor.Enter has an overload that takes a second argument of type Boolean, passed by reference, indicating whether the lock was taken.


Monitor.Enter locks the object; Monitor.Exit unlocks it. It is fundamental to place Monitor.Exit in the Finally part of the Try..Catch block so that resources will be unlocked anyway. At this point, you might wonder why you should use Monitor instead of SyncLock..End SyncLock because they produce the same result. The difference is that Monitor also exposes additional members, such as the TryEnter method that supports timeout, as demonstrated here:

Monitor.TryEnter(lockObject, 3000, result)

This code attempts to obtain the lock on the specified object for three seconds before terminating.

Read/Write Locks

A frequent scenario is when you have a shared resource that multiple reader threads need to access. In a scenario like this, you probably want to grant writing permissions just to a single thread to avoid concurrency problems. The .NET Framework provides the System.Threading.ReaderWriterLockSlim class, which provides a lock enabled for multiple threads reading and exclusive access for writing.


Readerwriterlock Class

The .NET Framework still provides the ReaderWriterLock class, as in its previous versions, but it is complex and used to handle particular multithreading scenarios. Instead, as its name implies, the ReaderWriterLockSlim class is a lightweight object for reading and writing locks.


An instance of this class is declared as a shared field and is used to invoke both methods for reading and writing. The following code demonstrates how you enable a writer lock:

Private Shared rw As New ReaderWriterLockSlim

Sub AddCustomer(ByVal customerName As String)
    Try

        rw.EnterWriteLock()
        customers.Add(customerName)
    Catch ex As Exception
    Finally
        rw.ExitWriteLock()
    End TrThe
End Sub

This ensures that only one thread can write to the customers’ collection. The next code snippet shows instead how you can enable a reader lock:

Sub GetInformation()
    Try
        rw.EnterReadLock()
        Console.WriteLine(customers.Count.ToString)
    Catch ex As Exception
    Finally
        rw.ExitReadLock()
    End Try
End Sub

ReaderWriterLockSlim is an object you should use if you expect more readers than writers; in other cases you should consider custom synchronization locks implementations.

Summary

This chapter covered processes management and multithreading with Visual Basic 2012. First, you saw how to utilize the System.Diagnostics.Process class for launching and managing external process from your applications, including programmatic access to processes. Next, you got an overview of threads and the System.Threading.Thread class, understanding how a thread is a single unit of execution and seeing how you create and run threads both programmatically and inside the .NET’s thread pool. In the final part of this chapter, you learned about synchronization locks, which are necessary so that multiple threads access the same resources concurrently. For this, remember the SyncLock..End SyncLock VB statement and the Monitor class. Understanding threads is particularly important in the .NET programming—especially to understand how the runtime can handle the application execution. But from the point of view of writing responsive applications, the .NET Framework offers more modern patterns: parallel computing and asynchronous programming. Without a knowledge of threading, it would be difficult to understand how both work. They are discussed in the next chapters.

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

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