Kaleb Klein

C# Threads

Created: Feb 08th, 2015, 6:54:43 PM UTC (9 years ago)

Like for share this blog post:

17 comments

This website is a programmer's website, so I figured it's time to post some stuff about software development, and other programmer stuff. The blog section will be about my exploits in this area, so let's begin.

UPDATE
I've updated the source code to include the Visual Basic language as well. Not a part of C#, but it's good to know how to work both languages.

This article will be about the C# language, and threads. When I started C# development, I mainly worked with WinForms. Threads in WinForms have a few different ways of building, executing, and destroying threads. When I began, I'd build my own threads, like so

Code Language: C#
  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Program p = new Program();
  6. p.Begin();
  7. }
  8.  
  9. public void Begin()
  10. {
  11. Thread thread = new Thread(ThreadMethod);
  12. thread.Start();
  13. }
  14.  
  15. private void ThreadMethod()
  16. {
  17. for(int i = 0; i < 100; i++)
  18. {
  19. Console.WriteLine("Number: " + (i + 1));
  20. }
  21. }
  22. }

Now, there's nothing wrong with building threads like this, but if you were to create this thread, and run it at another time, you've created a thread, and it's just sitting there. Other processes could be using the resources you've taken, and your application is hoarding it. Another issue, you'd have to make sure you destroy the thread when you're done, and if you don't, the resources aren't released for another process to use.

This is where the thread pool comes into play. The thread pool is a collection of threads that you can ask the tasking manager to give you. When you're done with it, it's given back to the pool, and is ready for use by another process. C# has a class that gives you access to this pool. The easiest way is by using the Threading.Task class.

Code Language: C#
  1. Task task = Task.Factory.StartNew(() => { ThreadMethod() });

Well, there's something new. The anonymous method that's being shown within the parameter section of the StartNew method, is whats called a lamda. Lamdas are shorthand anonymous methods that can be used in the event closures would become necessary. For this particular one, the ThreadMethod is being called. If you needed another method to fire off once the thread is complete, you can pass in a second method like so

Code Language: C#
  1. Task task = Task.Factory.StartNew(() => { ThreadMethod(); CompletedMethod(); });

It's that simple. Once all the methods in the lamda expression are complete, the thread is released and sent back to the thread pool.


So, let's take a look at a real world example. This example utilizes the thread pool, and updates the UI thread according to it's current progress within this thread. If you would like to check out the entire source code, you can download the VS project from here [cdn.kalebklein.com]. Keep in mind, Visual Studio 2013 and .NET 4.5 is required to compile this example code.

If you just want the compiled executables to visually see the examples, you can grab those here [cdn.kalebklein.com].

Let's take a look at the states of the appliation.

Start Progress Complete

So, let's take a look at the actual process of building this with the thread.

A few things to note, threads cannot interact with the UI. The UI is running on a sepearate thread, and only the UI thread can interact with them. Thankfully, C# makes it easier to interact with the UI from seperate threads via invocation. First, let's create our thread, along with our methods for this thread.

Code Language: C#
  1. private void button_click(object sender, EventArgs e)
  2. {
  3. Task.Factory.StartNew(() => { ThreadMethod(); Completed(); });
  4. }
  5.  
  6. // Method called when thread is started
  7. // All the thread does is done in this
  8. // method
  9. private void ThreadMethod()
  10. {
  11.  
  12. }
  13.  
  14. // Called when the thread has completed and closed
  15. private void Completed()
  16. {
  17.  
  18. }

Now, we need to work with our new thread. Let's attempt to update the UI from this thread.

Code Language: C#
  1. private void ThreadMethod()
  2. {
  3. int value = 0;
  4.  
  5. while(pbProgress.Value < pbProgress.Maximum)
  6. {
  7. pbProgress.Value = value;
  8. value++;
  9. }
  10. }

If we attempt to execute this thread, we'll get an exception. This is because we cannot interact with the UI thread's elements because our thread does not own them. So, we need to invoke a method that will handle this for us via the event dispatcher. To do so, we'll need a method that does this, we'll also need a delegate method used by the invoker to call this method.

Code Language: C#
  1. private delegate void UpdateProgressDelegate(int value);
  2.  
  3. private void UpdateProgress(int value)
  4. {
  5. pbProgress.Value = value;
  6. lblProgress.Text = String.Format("Progress: {0}%", value);
  7. }
  8.  
  9. private void ThreadMethod()
  10. {
  11. int value = 0;
  12. while(pbProgress.Value < pbProgress.Maximum)
  13. {
  14. if(!InvokeRequired)
  15. {
  16. UpdateProgress(value);
  17. }
  18. else
  19. {
  20. this.Invoke(new UpdateProgressDelegate(UpdateProgress), value);
  21. }
  22. value++;
  23. }
  24. }

Now if you run this, it should update the UI. The delegate method is used by the invoker to call the UpdateProgress method, and to pass the value variable that's used by the method for updating the UI. The complete code is as follows.

Code Language: C#
  1. public partial class Form1 : Form
  2. {
  3. public Form1()
  4. {
  5. InitializeComponent();
  6. }
  7.  
  8. // Delegate method called when thread is invoking UpdateProgress
  9. private delegate void UpdateProgressDelegate(int value);
  10.  
  11. // Updates the progress bar and label
  12. private void UpdateProgress(int value)
  13. {
  14. pbProgress.Value = value;
  15. lblProgress.Text = String.Format("Progress: {0}%", value);
  16. }
  17.  
  18. // Event listener for button click
  19. private void button_click(object sender, EventArgs e)
  20. {
  21. // Task created from thread pool
  22. // Task argument is a lamda expression with 2 methods:
  23. // The threaded method, and the closed method
  24. Task.Factory.StartNew(() => { ThreadMethod(); Completed(); });
  25. }
  26.  
  27. // Method called when thread is started
  28. private void ThreadMethod()
  29. {
  30. int value = 0;
  31. while(pbProgress.Value < pbProgress.Maximum)
  32. {
  33. // If the invoker is not required
  34. // Just call the update method
  35. if(!InvokeRequired)
  36. {
  37. UpdateProgress(value);
  38. }
  39. else
  40. {
  41. // Invoke the delegate if the invoker is required
  42. this.Invoke(new UpdateProgressDelegate(UpdateProgress), value);
  43. }
  44. value++;
  45. }
  46.  
  47. return;
  48. }
  49.  
  50. // Called when the thread has completed
  51. private void Completed()
  52. {
  53. MessageBox.Show("Process complete!");
  54. }
  55. }

The purpose of this post was to discuss threads in C#, and my experiences with it. Recently, I moved to WPF (Windows Presentation Foundation) from WinForms. One of the hardest things moving was threads. Threads follow a similar structure in WPF, however, communication between threads is a bit more strict. WPF also has different methods for invoking calles on different threads. The images below are similar to those above, but are done in WPF.

Start Progress Complete

Now, let's take a look at how this thread is created. You'll notice it's done the exact same way as WinForms.

Code Language: C#
  1. private void button_click(object sender, RoutedEventArgs e)
  2. {
  3. Task.Factory.StartNew(() => { ThreadMethod(); Completed(); });
  4. }

As you can see, there's no difference here. With that in mind, let's define the thread methods.

Code Language: C#
  1. private void ThreadMethod()
  2. {
  3.  
  4. }
  5.  
  6. private void Completed()
  7. {
  8.  
  9. }

Let's attempt to replicate the WinForms version of ThreadMethod

Code Language: C#
  1. private delegate void UpdateProgressDelegate(int value);
  2.  
  3. private void UpdateProgress(int value)
  4. {
  5. pbProgress.Value = value;
  6. lblProgress.Text = String.Format("Progress: {0}%", value);
  7. }
  8.  
  9. private void ThreadMethod()
  10. {
  11. int value = 0;
  12. while(pbProgress.Value < pbProgress.Maximum)
  13. {
  14. if(!InvokeRequired)
  15. {
  16. UpdateProgress(value);
  17. }
  18. else
  19. {
  20. this.Invoke(new UpdateProgressDelegate(UpdateProgress), value);
  21. }
  22. value++;
  23. }
  24.  
  25. return;
  26. }

If you attempt to run this, the compile will fail. That's because threads in WPF have different methods for invoking our delegates. Let's replace the old code with the new ones.

Code Language: C#
  1. while(pbProgress.Value < pbProgress.Maximum)
  2. {
  3. if(!Dispatcher.CheckAccess())
  4. {
  5. Dispatcher.Invoke(new UpdateProgressDelegate(UpdateProgress), value);
  6. }
  7. else
  8. {
  9. UpdateProgress(value);
  10. }
  11. }

If you run this now, it'll compile and run, but starting the thread will throw an exception. As I touched on earlier, WPF makes interacting with the UI more strict. The issue here is we're using the UI elements to get the value and maximum of our progress bar. To fix this, we'll create a couple getter methods that will utilize the dispatcher to get the values, and return them so we can use them in our thread.

Code Language: C#
  1. private int GetCurrentValue()
  2. {
  3. int value = 0;
  4. Dispatcher.Invoke(() =>
  5. {
  6. value = (int)pbProgress.Value;
  7. });
  8. return value;
  9. }
  10.  
  11. private int GetMaximumValue()
  12. {
  13. int max = 0;
  14. Dispatcher.Invoke(() =>
  15. {
  16. max = (int)pbProgress.Maximum;
  17. });
  18. return max;
  19. }

Now, we just need to replace the comparison in our while loop with these two methods

Code Language: C#
  1. while(GetCurrentValue() < GetMaximumValue())

And that's it! The application should now work correctly. Here is the complete code

Code Language: C#
  1. public partial class MainWindow : Window
  2. {
  3. public MainWindow()
  4. {
  5. InitializeComponent();
  6. }
  7.  
  8. // Delegate method called when thread is invoking UpdateProgress
  9. private delegate void UpdateProgressDelegate(int value);
  10.  
  11. // Update the progress bar and label
  12. private void UpdateProgress(int value)
  13. {
  14. pbProgress.Value = value;
  15. lblProgress.Text = String.Format("Progress: {0}%", value);
  16. }
  17.  
  18. // Gets current value of progress bar
  19. // Uses dispatcher to grab value
  20. private int GetCurrentValue()
  21. {
  22. int value = 0;
  23. Dispatcher.Invoke(() =>
  24. {
  25. value = (int)pbProgress.Value;
  26. });
  27. return value;
  28. }
  29.  
  30. // Get max value of progress bar
  31. // Uses dispatcher to grab value
  32. private int GetMaximumValue()
  33. {
  34. int max = 0;
  35. Dispatcher.Invoke(() =>
  36. {
  37. max = (int)pbProgress.Maximum;
  38. });
  39. return max;
  40. }
  41.  
  42. // Event listener for button click
  43. private void button_click(object sender, RoutedEventArgs e)
  44. {
  45. Task.Factory.StartNew(() => { ThreadMethod(); Completed(); });
  46. }
  47.  
  48. // Method called when thread is started
  49. private void ThreadMethod()
  50. {
  51. int value = 0;
  52. while(GetCurrentValue() < GetMaximumValue())
  53. {
  54. // If the dispatcher cannot access UI Elements
  55. // Use dispatcher to tell the UI to update itself
  56. if(!Dispatcher.CheckAccess())
  57. {
  58. Dispatcher.Invoke(new UpdateProgressDelegate(UpdateProgress), value);
  59. }
  60. else
  61. {
  62. // Update UI elements if dispatcher can do so
  63. UpdateProgress(value);
  64. }
  65. value++;
  66. }
  67.  
  68. return; // Return, Leave this method.
  69. }
  70.  
  71. // Method called when thread is closed
  72. private void Completed()
  73. {
  74. MessageBox.Show(String.Format("Process complete!"));
  75. }
  76. }

If you found this post helpful, or you just want to say something, please feel free to leave a comment below.


Leave a comment down below
(Required)
(Required) - For avatar image in comments area


BBCode tags you can use: [url] [code] [img] [p] [b] [i] [u] [s] [o] [size] [color] [center] [quote] /// What is BBCode?
Emoticons you can use
17
Comments (14 awaiting approval)


Tristan
Tristan:
Posted: Jul 26th 2015 (8 years ago)
"This is definitely something I'd find useful when I start in on C#. :D"
Kaleb
Kaleb:
Posted: Apr 09th 2015 (8 years ago)
"Any time man, I'm glad I was able to help you out a little."
Paul
Paul:
Posted: Mar 28th 2015 (9 years ago)
"I found this very helpful. I was a bit frustrated using the task manager and the differences between wpf and regular windows forms. Excellent post! Thanks for posting it and taking the time!"

Google+

Archive

Ads