C# Multi-Threading In A GUI Environment

C# Multi-Threading In A GUI Environment

Introduction

Hi everyone, and welcome to this spur of the moment tutorial!


Aim of this tutorial

The aim of this tutorial is to provide insight into the tools available for separating asynchronous and multi threaded work from the UI. The general idea is to separate the logical work out into a separate class(es), instantiate that class (initializing it with any state data required by the work operation), and then get the result, and update the GUI via a callback, in a nice, decoupled manner.

Despite this, the general patterns still apply even if you do not use a separate class to encapsulate the work, although I would argue that it is a good idea to use a separate class for any meaningful operation.


Moving on, the tutorial will build on  this  tutorial, but will introduce new techniques, of which aren't tightly coupled to the WinForms technology.


Required Knowledge

Before you begin this tutorial, you should have a solid base knowledge of all C#'s fundamental concepts, and have at least a general knowledge of the different API's available for performing multithreaded and asynchronous operations in C#. The examples are kept very very simple, but basic knowledge is still required  :)


Why I decided to write this tutorial

So, what has brought this on? Well, I responded to  this  thread made in the C# forum today about the standard cross thread communication issues that we have all faced when using multithreading in a GUI environment. For example, we have all seen code like this to marshal updates onto the UI thread, I am sure:

this.txtMyTextBox.Invoke(new MethodInvoker(() => this.txtMyTextBox.Text = "Updated Text!"));


Anyway,  tlhIn`toq  then made a very good point about how it is usually better to decouple the operation running on a background thread from the UI, so I'd like to provide a few examples  :bigsmile: , and expand on this point. 

The gist of the point was that rather than have the background thread directly update the UI directly using  Invoke() , you should try and favour having the background thread raise an event (of which contains the data used to update the UI), have the form subscribe to this event, and thus have the form update itself when the data from the background thread becomes available.

This would mean that the operation knows nothing of the UI, making the program more maintainable, and the operation itself reusable in the future. The UI is left with its single responsibility - updating the UI, and the class encapsulating the background operation is left with its single responsibility - perform the operation.


Although, even with events, you do still have to be aware of the cross thread communication issue, of course. If you simply raise an event (in the standard, everyday way) from the background thread, and have the form handle the event, the handler method will be run on that background thread, thus meaning you would still have to invoke UI updates onto the main thread.

This wouldn't be the end of the world, as you have still effectively decoupled the logical operation from the UI, but we would like to avoid even having to use  Invoke()  if possible, thus keeping the UI code as focused and simple as possible  :)


Examples

So, let's move onto the examples, staring with the events technique, and moving onto new techniques that have become available in .NET 4.0...


All the examples revolve around executing some arbitrary work, on a background thread, of which eventually completes at which point the GUI is alerted, and the  GUI is allowed to update itself, without needing to use Invoke() . They move progressively up from traditional techniques to newer techniques using features new to .NET 4.0.

I am going to encapsulate the work in a class called  WorkItem  (you could name it something more specific to your application. For example, you could have a class called PrimeNumberCalculator, have a method called Calculate() to calculate the numbers, and have an event called PrimesCalculated when the calculation was complete), and the work is always going to return a string as a result (just for example's sake, you can change this, or make it more general if you wish (return a generic Result  object perhaps...)). 

So, a button called  btnStart  is going to start the work going, and a textbox called  txtResult  is going to be updated with the result.

Note: For the sake of simplicity, I am going to start the threads, and have the actual work to perform, in the same class. This may be fine for many applications, but this technically limits flexibility, and violates the Single Responsiblity Principle (SRP) at the class level. This is particularly the case in example 1 below.

To achieve more clear separation, and fully adhere to the SRP at the class level, it would be more correct to completely separate the creating of the threads, from the actual work to perform. The BackgroundWorker class, for example, does this by asking you to provide the work to perform via an event that it raises.

However, in order to keep the focus on the tools available for working with threads in a GUI environment, I shall keep the code that creates the threads, and the actual work, in the same class, for this tutorial.
.

Example 1: Raising an event to update the UI - The More Traditional Approach

This concept is very closely related to the  Event Based Asynchronous pattern , and, thus, in many cases, you may be able to use built in classes that already implement that pattern (or perhaps sub class those classes, and add custom functionality). 

The  BackgroundWorker  class is a commonly used example of this pattern, of which is used to perform compute bound operations on a background thread.  Here  is a tutorial detailing how to use that class.

That class uses the thread pool (by calling the  BeginInvoke()  method on a WorkerThreadStartDelegate  instance).

This technique is still very worthwhile to know how to implement yourself though. For example, if you don't want to use the thread pool, you may want to create your own, dedicated class. Or, you may just want to make your own class with domain specific events and classes, as it fits better with your scenario/program.

Plus, it doesn't hurt to get an idea of how classes like the  BackgroundWorker  work internally (at a general level), and it demonstrates the point  tlhIn`toq  was making.


Let's start with the basic EventArg subclass that will hold the result of our operation (it only has one constructor for this example, you can add more). 


1 //event args containing the result of the WorkItem's work
2 public class WorkItemCompletedEventArgs : EventArgs {
3  
4     public string Result { getset; }
5  
6     public WorkItemCompletedEventArgs(string result) {
7        this.Result = result;
8     }
9 }


Pretty straight forward. Its just a class to hold the result our work operation produces.

Next, the  WorkItem  class, of which will actually do our work.

Note: if you wanted to pass arguments (state data) for use in the operation, you could store them arguments in properties in this class, passing them in via the WorkItemconstructor maybe(???). You can then use those properties in your operation performed by DoWork.


01 public class WorkItem {
02     //async operation representing the work item
03     private AsyncOperation op;
04  
05     //event handler to be run when work has completed with a result
06     public event EventHandler<WorkItemCompletedEventArgs> Completed;
07  
08     public void DoWork() {
09         //get new async op object ***from current synchronisation context***
10         //which is the caller's sync context (i.e. the form's)
11         this.op = AsyncOperationManager.CreateOperation(null);
12         //queue work so a thread from the thread pool can pick it
13         //up and execute it
14         ThreadPool.QueueUserWorkItem((o) => this.PerformWork);
15     }
16  
17     private void PerformWork() {
18       //do work here...
19       //The work could use passed state data
20       //held in properties of this class
21       //if we needed to pass in data from the UI
22       //for example
23         Thread.Sleep(5000);
24         //once completed, call the post completed method, passing in the result
25         this.PostCompleted("Update with result!");
26     }
27  
28  
29     private void PostCompleted(string result) {
30         //complete the async operation, calling OnCompleted, passing in the result to it
31         // The lambda passed into this method is invoked on the synchronisation context
32         //the async operation was created on (i.e. the form's)
33         op.PostOperationCompleted((o) => this.OnCompleted(newWorkItemCompletedEventArgs(o.ToString())), result);
34     }
35  
36  
37     protected virtual void OnCompleted(WorkItemCompletedEventArgs e) {
38         //raise the Completed event ***on the form's synchronisation context***
39         EventHandler<WorkItemCompletedEventArgs> temp = this.Completed;
40         if (temp != null) {
41             temp.Invoke(this, e);
42         }
43     }
44 }


Now, here is the form's code:

01 public partial class Form1 : Form {
02         
03         private string Result { get return this.txtResult.Text; } set {this.txtResult.Text = value; } }
04  
05         public Form1() {
06             InitializeComponent();
07         }
08  
09         private void btnStart_Click(object sender, EventArgs e) {
10             //start work
11             this.StartBackgroundWork();
12         }
13  
14         private void StartBackgroundWork() {
15             //create new work item
16             //We could pass in any state data to use in the
17             //operation into the constructor here. We'd have
18             //to write the constructor first through, obviously ;)/>
19             WorkItem item = new WorkItem();
20             //subscribe to be notified when result is ready
21             item.Completed += item_Completed;
22             //start work going from form
23             item.DoWork();
24         }
25  
26         //handler method to run when work has completed
27         private void item_Completed(object sender, WorkItemCompletedEventArgs e) {
28             //GUI is free to update itself
29             this.Result = e.Result;
30         }
31     }


Right, so let's go through what happens when the user presses the start button...

1)  StartBackgroundWork()  on the form is called.

2)  StartBackgroundWork()  creates a new  WorkItem  instance, and subscribes to its  Completed event. So, when that  WorkItem  raises the  Completed  event, the  item_Completed  method will be called. Finally, it sets the work going by calling  DoWork()

Note how the form has no idea how the work is done. All it knows is that the work will be completed with a result, sometime in the future.

3)  DoWork()  first captures the current  synchronisation context , and encapsulates in a AsyncOperation  object. As  DoWork()  was called from the form, this means that it captures the context of the UI thread. This  AsyncOperation  object, quite simply, represents our operation! Finally,  DoWork()  calls  PerformWork()  on a new background thread from the thread pool.

4)  PerformWork()  simulates genuine work by sleeping the thread, and then calls  PostCompleted() , passing in the result of the work (which is a hardcoded string in this example).

5)  PostCompleted()  calls  PostOperationCompleted()  on the  AsyncOperation  we created in DoWork() . What this does is calls the lambda exp ression specified in the first arguement (passing in the result string to that lambda via the second argument),  on the synchronisation context we captured when we created the AsyncOperation object . Thus, we are now back on the UI thread. So, when the lambda exp ression calls  OnCompleted() , it is run on the UI thread. We also create a new  WorkItemCompletedEventArgs  instance to wrap our string result.

6)  OnCompleted()  then raises our  Completed  event (passing to it the WorkItemCompletedEventArgs  instance containing our result), which calls  item_Completed   on the UI thread  (remember, the form registered to be notified when the  Completed  event was raised).

7) As we are on the UI thread,  item_Completed  is free to update the UI without  Invoke() .

The beauty of that is that the UI knows nothing of how the  WorkItem  does its work, so it can just concentrate on handling the UI. Further, the  WorkItem  knows nothing of the UI, and so can be reused with an infinite number of different projects.

That is generally how all the event based asynchronous classes work (WebClient, BackgroundWorker etc), and is a nice, clear pattern to use to produce well designed, asynchronous software.


Example 2: Using new .NET 4.0 Task to perform operation on thread pool thread

I mentioned that the  AsyncOperation  represents our asynchronous operation. However, .NET 4.0 provides an optimised, easy to use class that abstracts the idea of an asynchronous operation further. It is the  Task  class, and is the central part of a new API called the  Task Parallel Library (TPL)

We can implement the above pattern in a similar way, without events (albeit using the same general concept of callbacks). However, Tasks provide a number of beneficial, easy to use features that stand it apart from  BackgroundWorker  class, and the event based pattern in general. I wrote a basic introductory tutorial  here .


Let me demonstrate by converting the above example to use the TPL:

01 //notice, this class is greatly simplified using Tasks
02     public class WorkItem {
03          
04         public Task<string> DoWork() {
05             //create task, of which runs our work of a thread pool thread
06             return Task.Factory.StartNew<string>(this.PerformWork);
07         }
08  
09         private string PerformWork() {
10             Thread.Sleep(5000);//do work here...
11             //return result of work
12             return "Update with result!";
13         }
14     }


Notice how much simpler our  WorkItem  class is now.

Here is our form class:

01 public partial class Form1 : Form {
02  
03         public string Result { get return this.txtResult.Text; } set {this.txtResult.Text = value; } }
04  
05         public Form1() {
06             InitializeComponent();
07         }
08  
09         private void btnStart_Click(object sender, EventArgs e) {
10             //start work going, and register a call back using ContinueWith() that is called
11             //when the work completes, of which updates the UI with the result
12             this.StartBackgroundWork()
13                                   .ContinueWith((t) => this.Result = t.Result, TaskScheduler.FromCurrentSynchronizationContext()); ;
14         }
15  
16         private Task<string> StartBackgroundWork() {
17             //create new work item, start work and return
18             //the task representing the asynchronous work item
19             return new WorkItem().DoWork();
20         }
21     }


Right, let's go through that now, starting at the  WorkItem  class this time...

DoWork()  starts a Task (of which returns a string), of which grabs a background thread from the thread pool. That thread then runs  PerformWork() , of which does the work and returns the resulting string, as before. 

Notice, however, that  DoWork()  returns the  Task<string>  to the caller. So, we are returning our abstract asynchronous operation to the caller. However, this operation may (if now exceptions occur etc) produce a result in the future, and we want the UI to update itself with the result. 

Well, the  Task<string>  representing the operation is returned to the UI, so it has access to it, but how do we get the result?

Easy! We register a callback that is run when the operation completes (this is essentially what we were doing with the  Completed  event in the event based example).

We do this by calling  ContinueWith()  on the  Task<string> . The lambda passed to ContinueWith()  will be called when the  Task<string>  completes, and that completed task will be passed to it, so we can get its result. 

In that lambda, we update the UI with that result. However, notice this line:

TaskScheduler.FromCurrentSynchronizationContext();

Remember how we used an  AsyncOperation  object to capture the UI's synchronisation context so we could update the UI from the UI thread. Well, that is what that line is doing. It is saying, 'run this callback (represented by the lambda passed as the first arguement to  ContinueWith() ) on the current synchronisation context.' The current context is the UI thread, so that callback (and thus the code to update our textbox), is run on the UI thread, avoiding the need for  Invoke() .

So, the  Task  class provides a potentially easier alternative to using standard events. Plus, the Task  class has been optimised to work with the thread pool (which will be more efficient than firing up a dedicated thread using the  Thread  class, as you can reuse previous threads that are sitting in the thread pool, instead of creating a brand new thread every time (which is expensive)).

In fact, the TPL (Task Parallel Library), of which Tasks are central to, is currently the only API that makes use of certain optimisations to the thread pool that the CLR team made. It offers more features the the  BackgroundWorker  class, and the event based pattern in general.


Example 3: Wrapping a none Task implementation in a Task 

The  Task  API (TPL (Task Parallel Library)) is all well and good. However, sometimes, the thread pool (of which Tasks use) cannot really be used for your operations.

For example, if you have a loop that has many iterations (1000+), and each iteration fires up a Task  that may take a while to complete, you run the risk of starving the thread pool, and running out of threads (I think the pool currently has a default maximum (in a 32-bit process) of  around 2000 threads (this is always subject to change by Microsoft). You can change that, but it isn't usually a great idea to do so). 

If you have a very long running task, it is potentially better, and more efficient to start a brand new, dedicated thread, rather than tie up a thread pool thread. However, you then lose the benefit of having the 'fluffy', simple to use Task object to work with.

So, what to do? 

Well, you can use the  Thread  class to start work items going and do the work, but wrap the implementation in a  Task , so that callers can work with a Task, thus getting the practical benefits of using the  Thread  class to run a long running piece of computationally intensive work, but getting the ease of use of the TPL API also!

To do this, here is the updated  WorkItem  class:

01   public class WorkItem {
02     //this produces a task for us
03     private TaskCompletionSource<string> completionSource;
04  
05     public Task<string> DoWork() {
06         //create a new source of tasks
07         this.completionSource = new TaskCompletionSource<string>();
08         //start work going using ***Thread class****...
09         new Thread(this.PerformWork).Start();
10         //...however, return a task from the source to the caller
11         //so they get to work with the easy to use Task.
12         //We are providing a Task facade around the operation
13         //running on the dedicated thread
14         return this.completionSource.Task;
15     }
16  
17     private void PerformWork() {
18         Thread.Sleep(5000);//do work here...
19         //set result of the Task here, which completes the task
20         //and thus schedules any callbacks the called registered
21         //with ContinueWith to run
22         this.completionSource.SetResult("Update with result!");
23     }
24 }


The Form class stays exactly as it was in the previous (Task) example, as we are still returning a Task<string> to the UI.

So, what are we doing here?

Well, when  DoWork()  is called, an new  TaskCompletionSource<string>  object is created. This object has the capability to produce a  Task<string>  object for us, on demand. 

So, we start the work going on a background thread  using the Thread class (nothing to do with the Task class) , and we then grab a  Task<string>  from the TaskCompletionSource<string>  object, and return that to the caller (the UI).

Therefore, the UI still has its  Task<string>  representing the operation, and it can register callbacks using  ContinueWith()  and do everything that the  Task<T>  class allows!

What about when the work is finished though? Even though the caller has a  Task<string> , we aren't actually using a  Task<string>  to do our work. We are using the  Thread  class! So, how do we signal that the work has completed, allowing any  ContinueWith()  callbacks the calling  Form class has registered to run?

Simple! We call  SetResult()  (passing in the hardcoded string result) on the TaskCompletionSource<string>  object that produced the  Task<string> . That transitions the Task<string>  that the UI is working with into a 'completed' state, and schedules any registered callbacks to run!


I find this quite spectacular actually. The  Task<T>  class provides a very easy to use interface for us developers to interact with. Now, with  TaskCompletionSource<string> , we can wrap  any  operation we want in a Task!

We can change the  WorkItem  class to use the  Thread  class behind the scenes, instead of the ThreadPool  class, and the  Form  class would never know, as it would still be getting its Task<string>  so it would be happy. Hell, we can even change the  WorkItem  class to use the BackgroundWorker  class behind the scenes, or we could even run the work synchronously, with no extra threads at all, if we really wanted!


Quick note on I/O vs Compute Bound Operations

The  BackgroundWorker  class, and the  Thread  and  ThreadPool  classes are for compute bound operations. So, what if you have an I/O bound operation (reading from a file, for example)? 

Well, firstly, note that running IO bound operations in a dedicated background thread is wasteful, as the executing thread just sits blocked, doing nothing, while the I/O subsystem completes the operation), but having the friendly Task interface to work with, and sign up callbacks with as well!

Because of this potential inefficiency, .NET provides methods that perform asynchronous I/O. This is asynchrony without the use of a dedicated thread, using I/O completion ports. 

The key example of these methods are in the  APM  API. It uses pairs of methods that take the following pattern - BeginXXX()/EndXXX() to perform asynchronous I/O in the most efficent way possible (using I/O completion ports).

How can we use these with our Task's though? Well, we can use the technique shown in the previous example to wrap the APM pattern in a Task facade. This means we gain the efficiency benefits of asynchronous I/O for our I/O bound operation, but maintain many of the benefits of the TPL.

(Note: For a little more info on why you shouldn't technically use the a dedicated thread) for I/O bound operations, see  here ).


Happily, the  TaskFactory<T>  and  TaskFactory  classes have actually already had methods built in for this I/O bound scenario. The  FromAsync()  methods of those classes allow you to wrap I/O bound operations implementing the BeginXXX()/EndXXX() APM pattern in a Task facade automatically for you


FromAsync()  uses  TaskCompletionSource<T>  behind the scenes to wrap the APM implementation in a Task object.

An introduction to the  FromAsync()  method is given  here , and see  here  also.



Anyway, the point to take from this is that we can put a Task facade around any operation (I/O bound or compute bound), and this provides unbelievable flexibility, as it means you can use tasks for ANY arbitrary operation, and I believe this is what you should now do (if using .NET 4.0) when looking to introduce asynchrony into your applications!


Example 4: New Async CTP

I couldn't talk about this topic without briefly mentioning the new  Async CTP  (note that it is still only a CTP at the moment, so no guarantees are made with it, and you have to download the .dll to use it).

If (or rather, when) this pattern does become a mainstream part of the language, it's going to make things like this even more awesome! It is based  heavily  on the  TPL , and so any  Task implementation can be converted to use this pattern.

For example, both the ordinary Task implementation and the 'Task Wrapper' implementation (shown in example 2 and 3 respectively above) can be simplified further (and converted to the new async pattern) just by changing the  btnStart_Click  method handler to this:

1 private async void btnStart_Click(object sender, EventArgs e) {
2      this.Result = await this.StartBackgroundWork();
3 }


No (visible) callbacks are now needed in your code, and no changes were made to any of the other code from example 2 or 3!.  I simply inserted two strategically placed contextual keywords into the above method, and removed the  ContinueWith()  call. 

That is the beauty of this pattern. You can hardly tell the difference in the calling code between synchronous and asynchronous code any more! The calling code is almost completely unaware that it is calling a method asynchronously!

In short, Tasks are the way .NET asynchrony is going, and I think you should first turn to them when looking to perform  any  asynchronous operation; compute bound (using standard tasks to grab a thread pool thread to run the work on) OR I/O bound (wrap the asynchronous I/O methods that exist in the framework in a Task facade. Actually, this is particularly relevant for APM pattern, which typically does not produce very readable code. The Task wrapper would add readability and ease of use  :) ).


Conclusion

So there you go, 4 different ways to achieve better designed (all demonstrating the same general pattern), loosely coupled asynchronous code going forward, and there is no  Invoke()  anywhere in sight  :)  It may not always be totally viable (or even necessary), but where it is, it is generally a good idea to try and decouple the caller (UI) from any meaningful operation running on a background thread. 

The examples given are in the simplest form, but demonstrate the general tools and ideas available, of which you can adapt to your application. 

So to summarize, generally (there will naturally be exceptions), I would use example 1's event based technique (potentially using the built in BackgroundWorker for compute bound operations, depending on the scenario) if I wasn't using .NET 4.0 (or greater). If I had .NET 4.0 (or greater) at my disposal, (at the moment) I would use the technique of creating tasks used in example 2 for compute bound operations, and example 3's technique for I/O bound operations. If and when the async CTP becomes a mainstream part of the language, I would use that for pretty much everything!

Further, you can change the UI (add new controls, take controls away, rename controls, even switch to a different UI technology altogether (switch from WinForms to WPF, for example)), and it won't effect the class containing the operation logic in any way!


Thanks for reading! 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
辽B代驾管理系统对代驾订单管理、用户咨询管理、代驾订单评价管理、代驾订单投诉管理、字典管理、论坛管理、公告管理、新闻信息管理、司机管理、用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行辽B代驾管理系统程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。辽B代驾管理系统的开发让用户查看代驾订单信息变得容易,让管理员高效管理代驾订单信息。 辽B代驾管理系统具有管理员角色,用户角色,这几个操作权限。 辽B代驾管理系统针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理代驾订单信息,管理公告信息等内容。 辽B代驾管理系统针对用户设置的功能有:查看并修改个人信息,查看代驾订单信息,查看公告信息等内容。 辽B代驾管理系统针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理代驾订单信息,管理公告信息等内容。 辽B代驾管理系统针对用户设置的功能有:查看并修改个人信息,查看代驾订单信息,查看公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看代驾订单,删除代驾订单操作,新增代驾订单操作,修改代驾订单操作。公告信息管理页面提供的功能操作有:新增公告,修改公告,删除公告操作。公告类型管理页面显示所有公告类型,在此页面既可以让管理员添加新的公告信息类型,也能对已有的公告类型信息执行编辑更新,失效的公告类型信息也能让管理员快速删除。新闻管理页面,此页面提供给管理员的功能有:新增新闻,修改新闻,删除新闻。新闻类型管理页面,此页面提供给管理员的功能有:新增新闻类型,修改新闻类型,删除新闻类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值