Responsive applications are a must in today's programming approach. They can improve the performance of the application itself and make our application have a user-friendly interface. We need to asynchronously run the code execution process in our program to have a responsive application. To achieve this goal, in this chapter, we will discuss the following topics:
The first time .NET Framework was announced, the flow of the program was executed sequentially. The drawback of this execution flow is that our application has to wait for the operation to finish before executing the next operation. It will freeze our application, and that will be an unpleasant user experience.
To minimize this problem, .NET Framework introduces thread, the smallest unit of execution, which can be scheduled independently by the OS. And the asynchronous programming means that you run a piece of code on a separate thread, freeing up the original thread and doing other things while the task is completed.
Let's start our discussion by creating a program that will run all operations synchronously. The following is the code that demonstrates the synchronous operation that we can find in the SynchronousOperation.csproj
project:
public partial class Program { public static void SynchronousProcess() { Stopwatch sw = Stopwatch.StartNew(); Console.WriteLine( "Start synchronous process now..."); int iResult = RunSynchronousProcess(); Console.WriteLine( "The Result = {0}",iResult); Console.WriteLine( "Total Time = {0} second(s)!", sw.ElapsedMilliseconds/1000); } public static int RunSynchronousProcess() { int iReturn = 0; iReturn += LongProcess1(); iReturn += LongProcess2(); return iReturn; } public static int LongProcess1() { Thread.Sleep(5000); return 5; } public static int LongProcess2() { Thread.Sleep(7000); return 7; } }
As we can see in the preceding code, the RunSynchronousProcess()
method executes two methods; they are the LongProcess1()
and LongProcess2()
methods. Now let's call the preceding RunSynchronousProcess()
method, and we will get the following output on the console:
These two methods, LongProcess1()
and LongProcess2()
, are independent, and each method takes a particular time to finish. Since it is executed synchronously, it takes 12 seconds to finish these two methods. The LongProcess1()
method needs 5 seconds to finish, and the LongProcess2()
method needs 7 seconds to finish.
We can improve our previous code so that it can be the responsive program by refactoring some of the code and adding threads to the code. The refactored code would be as follows, which we can find in the ApplyingThreads.csproj
project:
public partial class Program { public static void AsynchronousProcess() { Stopwatch sw = Stopwatch.StartNew(); Console.WriteLine( "Start asynchronous process now..."); int iResult = RunAsynchronousProcess(); Console.WriteLine( "The Result = {0}", iResult); Console.WriteLine( "Total Time = {0} second(s)!", sw.ElapsedMilliseconds / 1000); } public static int RunAsynchronousProcess() { int iResult1 = 0; // Creating thread for LongProcess1() Thread thread = new Thread( () => iResult1 = LongProcess1()); // Starting the thread thread.Start(); // Running LongProcess2() int iResult2 = LongProcess2(); // Waiting for the thread to finish thread.Join(); // Return the the total result return iResult1 + iResult2; } public static int LongProcess1() { Thread.Sleep(5000); return 5; } public static int LongProcess2() { Thread.Sleep(7000); return 7; } }
As we can see, we refactor the RunSynchronousProcess()
method in the previous code into the RunAsynchronousProcess()
method. And if we run the RunAsynchronousProcess()
method, we will get the following output on the console:
Compared to the RunSynchronousProcess()
method, we now have a faster process in the RunAsynchronousProcess()
method. We create a new thread that will run the LongProcess1()
method. The thread will not run until it has started using the Start()
method. Take a look at the following code snippet, in which we create and run the thread:
// Creating thread for LongProcess1() Thread thread = new Thread( () => iResult1 = LongProcess1()); // Starting the thread thread.Start();
After the thread is run, we can run the other operation, in this case, the LongProcess2()
method. When this operation is done, we have to wait for the thread to be finished so that we use the Join()
method from the thread instance. The following code snippet will explain this:
// Running LongProcess2() int iResult2 = LongProcess2(); // Waiting for the thread to finish thread.Join();
The Join()
method will block the current thread until the other thread that's being executed is finished. After the other thread finishes, the Join()
method will return, and then the current thread will be unblocked.
Besides using the thread itself, we can also precreate some threads using the System.Threading.ThreadPool
class. We use this class if we need to work with threads from the thread pool. When using thread pool, you are more likely to use only the QueueUserWorkItem()
method. This method will add an execution request to the thread pool queue. If any threads are available in the thread pool, the request will execute right away. Let's take a look at the following code in order to demonstrate the use of thread pool, which we can find in the UsingThreadPool.csproj
project:
public partial class Program { public static void ThreadPoolProcess() { Stopwatch sw = Stopwatch.StartNew(); Console.WriteLine( "Start ThreadPool process now..."); int iResult = RunInThreadPool(); Console.WriteLine("The Result = {0}", iResult); Console.WriteLine("Total Time = {0} second(s)!", sw.ElapsedMilliseconds / 1000); } public static int RunInThreadPool() { int iResult1 = 0; // Assignin work LongProcess1() to idle thread // in the thread pool ThreadPool.QueueUserWorkItem((t) => iResult1 = LongProcess1()); // Running LongProcess2() int iResult2 = LongProcess2(); // Waiting the thread to be finished // then returning the result return iResult1 + iResult2; } public static int LongProcess1() { Thread.Sleep(5000); return 5; } public static int LongProcess2() { Thread.Sleep(7000); return 7; } }
In the thread pool, we can invoke the QueueUserWorkItem()
method to put a new work item in a queue, which is managed by the thread pool when we need to run the long running process instead of creating a new thread. There are three possibilities of how the work is treated when we send it to thread pool; they are as follows:
MaxThreads
property has not been reached yet so the thread pool will create a new thread, assign the work, and run the work immediately.MaxThreads
. In this situation, the work item will wait in the queue for the first available thread.Now, let's run the ThreadPoolProcess()
method, and we will get the following output on the console:
As we can see in the preceding screenshot, we get the same result with the similar process time we get when we apply the new thread that we discussed in the previous section.