Like for share this blog post:
23 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
class Program { static void Main(string[] args) { Program p = new Program(); p.Begin(); } public void Begin() { Thread thread = new Thread(ThreadMethod); thread.Start(); } private void ThreadMethod() { for(int i = 0; i < 100; i++) { Console.WriteLine("Number: " + (i + 1)); } } }
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.
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
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.
private void button_click(object sender, EventArgs e) { Task.Factory.StartNew(() => { ThreadMethod(); Completed(); }); } // Method called when thread is started // All the thread does is done in this // method private void ThreadMethod() { } // Called when the thread has completed and closed private void Completed() { }
Now, we need to work with our new thread. Let's attempt to update the UI from this thread.
private void ThreadMethod() { int value = 0; while(pbProgress.Value < pbProgress.Maximum) { pbProgress.Value = value; value++; } }
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.
private delegate void UpdateProgressDelegate(int value); private void UpdateProgress(int value) { pbProgress.Value = value; lblProgress.Text = String.Format("Progress: {0}%", value); } private void ThreadMethod() { int value = 0; while(pbProgress.Value < pbProgress.Maximum) { if(!InvokeRequired) { UpdateProgress(value); } else { this.Invoke(new UpdateProgressDelegate(UpdateProgress), value); } value++; } }
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.
public partial class Form1 : Form { public Form1() { InitializeComponent(); } // Delegate method called when thread is invoking UpdateProgress private delegate void UpdateProgressDelegate(int value); // Updates the progress bar and label private void UpdateProgress(int value) { pbProgress.Value = value; lblProgress.Text = String.Format("Progress: {0}%", value); } // Event listener for button click private void button_click(object sender, EventArgs e) { // Task created from thread pool // Task argument is a lamda expression with 2 methods: // The threaded method, and the closed method Task.Factory.StartNew(() => { ThreadMethod(); Completed(); }); } // Method called when thread is started private void ThreadMethod() { int value = 0; while(pbProgress.Value < pbProgress.Maximum) { // If the invoker is not required // Just call the update method if(!InvokeRequired) { UpdateProgress(value); } else { // Invoke the delegate if the invoker is required this.Invoke(new UpdateProgressDelegate(UpdateProgress), value); } value++; } return; } // Called when the thread has completed private void Completed() { MessageBox.Show("Process complete!"); } }
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.
private void button_click(object sender, RoutedEventArgs e) { Task.Factory.StartNew(() => { ThreadMethod(); Completed(); }); }
As you can see, there's no difference here. With that in mind, let's define the thread methods.
private void ThreadMethod() { } private void Completed() { }
Let's attempt to replicate the WinForms version of ThreadMethod
private delegate void UpdateProgressDelegate(int value); private void UpdateProgress(int value) { pbProgress.Value = value; lblProgress.Text = String.Format("Progress: {0}%", value); } private void ThreadMethod() { int value = 0; while(pbProgress.Value < pbProgress.Maximum) { if(!InvokeRequired) { UpdateProgress(value); } else { this.Invoke(new UpdateProgressDelegate(UpdateProgress), value); } value++; } return; }
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.
while(pbProgress.Value < pbProgress.Maximum) { if(!Dispatcher.CheckAccess()) { Dispatcher.Invoke(new UpdateProgressDelegate(UpdateProgress), value); } else { UpdateProgress(value); } }
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.
private int GetCurrentValue() { int value = 0; Dispatcher.Invoke(() => { value = (int)pbProgress.Value; }); return value; } private int GetMaximumValue() { int max = 0; Dispatcher.Invoke(() => { max = (int)pbProgress.Maximum; }); return max; }
Now, we just need to replace the comparison in our while loop with these two methods
while(GetCurrentValue() < GetMaximumValue())
And that's it! The application should now work correctly. Here is the complete code
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } // Delegate method called when thread is invoking UpdateProgress private delegate void UpdateProgressDelegate(int value); // Update the progress bar and label private void UpdateProgress(int value) { pbProgress.Value = value; lblProgress.Text = String.Format("Progress: {0}%", value); } // Gets current value of progress bar // Uses dispatcher to grab value private int GetCurrentValue() { int value = 0; Dispatcher.Invoke(() => { value = (int)pbProgress.Value; }); return value; } // Get max value of progress bar // Uses dispatcher to grab value private int GetMaximumValue() { int max = 0; Dispatcher.Invoke(() => { max = (int)pbProgress.Maximum; }); return max; } // Event listener for button click private void button_click(object sender, RoutedEventArgs e) { Task.Factory.StartNew(() => { ThreadMethod(); Completed(); }); } // Method called when thread is started private void ThreadMethod() { int value = 0; while(GetCurrentValue() < GetMaximumValue()) { // If the dispatcher cannot access UI Elements // Use dispatcher to tell the UI to update itself if(!Dispatcher.CheckAccess()) { Dispatcher.Invoke(new UpdateProgressDelegate(UpdateProgress), value); } else { // Update UI elements if dispatcher can do so UpdateProgress(value); } value++; } return; // Return, Leave this method. } // Method called when thread is closed private void Completed() { MessageBox.Show(String.Format("Process complete!")); } }
If you found this post helpful, or you just want to say something, please feel free to leave a comment below.
Emoticons you can use