Eclipse, as a development platform, is widely used. It is a client developed based on Eclipse rich client platform.ProgramMore and more. In today's increasingly complex application environments, our client programs inevitably need to process multiple tasks at the same time. An excellent client program allows you to start multiple tasks at the same time, which greatly improves your work efficiency and user experience. In this article, we will talk about how to implement multiple tasks in eclipse.
In our eclipse-based Java program, we have many ways to implement multiple tasks. A friend familiar with Java will immediately think of the Java Thread class, which is the most commonly used class in Java to implement multiple tasks. The eclipse platform provides APIs for multi-task processing, namely job and uijob. The job in eclipse is an encapsulation of Java thread, providing a more convenient interface for implementing multiple tasks. The basic usage of a job is as follows:
Listing 1. Job usage example
Job job = new job ("job name") {protected istatus run (iprogressmonitor Monitor) {// Add your job hereCodeReturn status. OK _status ;}}; job. Schedule (delaytime ); |
In eclipse, we often use display. asynchexec () and display. synchexec () to start task execution. These two methods are mainly used to facilitate the task of interface operations. The following is the usage of display. asynchexec (). display. synchexec () is similar to it.
Listing 2. Examples of display. synchexec () Usage
Display. getdefault (). asyncexec (New runnable () {public void run () {// Add your task code here }}); |
Generally, in eclipse, we 'd better use the job interface provided by eclipse to implement multi-tasking, rather than using Java thread. Why? There are mainly the following reasons:
- A job is a reusable unit of work. A job can be easily executed multiple times.
- Job provides a convenient interface for us to easily communicate with the outside world and report the current execution progress.
- Eclipse provides a mechanism for programmers to easily intervene in job scheduling. For example, we can easily implement that only one job of the same type is running at a time.
- By default, eclipse provides a Job Management Program to view all current jobs and their progress. It also provides the UI to terminate, pause, and continue specified jobs.
- Job can improve program performance and save the overhead of thread creation and destruction. The job in eclipse encapsulates the implementation of the thread pool. When we start a job, eclipse will not create a new thread immediately. It will find whether there are Idle threads in its thread pool. If there are Idle threads, it will directly run your job with Idle threads. When a job is terminated, its corresponding thread will not be terminated immediately, and it will be returned to the thread pool for reuse. In this way, we can save the thread creation and destruction overhead.
Next we will discuss the implementation and use of jobs in eclipse from several aspects.
Implementation of jobs in eclipse
The core package of eclipse provides a jobmanager class, which implements the ijobmanager interface. In eclipse, job management and scheduling are implemented by jobmanager. Jobmanager maintains a thread pool to run jobs. After the schedule method of the job is called, the job will be put in the waiting queue of a job by jobmanager. Then, jobmanager notifies the thread pool that a new job is added to the running waiting queue. The thread pool will find an idle thread to run the job. If there is no idle thread, the thread pool will create a new thread to run the job. Once the job is completed, the thread that runs the job will return to the thread pool for future use. From the process of running the job above, we can see that jobmanager is involved in the whole process of running a job. It understands when the job starts, when it ends, and the job running status at each time. Jobmanager provides the running information of these jobs to users through interfaces, and also provides interfaces for us to intervene in job scheduling, therefore, we have more powerful job control capabilities.
To make it easier for us to understand the status of a job, jobmanager sets a status flag of the job. We can get the current status value of the job through the getstate method of the job to understand its status:
- None: when a job is constructed, it is in this State. After a job is executed (including canceled), the job status changes back to this status.
- Waiting: When we call the shedule method of a job, jobmanager puts the job in the waiting Job Queue. In this case, the job status is waiting.
- Running: when a job starts to run, the job status changes to running.
- Sleeping: When we call the sleep method of a job, the job changes to this State. When we call the schudule method, we bring the delayed parameter, and the job status will also be transferred to this status. During this delay wait time, the job is in this status. This is a sleep state in which a job cannot be immediately transferred to the running state. We can call the wakeup method of the job to wake up the job. In this way, the job will be transferred to the waiting status for running.
Ui thread in eclipse
In addition, there is a UI thread concept in eclipse thread processing. The main thread in the eclipse program is a special thread. After the program starts, it will first execute this thread, that is, the thread where our main () function is located. As a desktop application, our main thread is mainly responsible for interface response and drawing interface elements, so we usually call it a UI thread.
The following code is familiar to readers who have compiled SWT applications. It usually appears at the end of the main function. Next, let's take a closer look at its details.
// While (! Shell. isdisposed () {// if no waiting event exists in the display object event queue, the thread enters the waiting state if (! Display. readanddispatch () display. Sleep ();} |
The above program is actually the processing logic of our UI thread: when the program starts, the UI thread will read the event and wait for the queue to see if there are any events waiting for processing. If yes, it will be processed accordingly, and if not, it will enter sleep state. If a new event arrives, it will be awakened for processing. The events to be processed by the UI thread include the mouse and keyboard operation events of the user, and the draw events issued by the operating system or program. Generally, the process of event processing is the process of responding to user operations.
A good desktop application requires the fastest response to user operations, that is, our UI thread must process various events as soon as possible. From the perspective of our program, we cannot perform a large amount of computing or waiting in the UI thread, otherwise the user operation events cannot be processed in a timely manner. In general, if there is a large amount of computing or it takes a long time to wait (such as network operations or database operations), we must separate these long-processing programs into a thread for execution. In this way, operations on the interface are not affected even though the program is running in the background.
All threads except the main thread are non-UI threads. In the eclipse program, all operations on interface elements must be executed in the UI thread; otherwise, an exception will be thrown. Therefore, we need to differentiate the UI thread and non-UI thread, ensure that all UI operations are executed in the UI thread.
How to determine whether the current thread is a UI thread: You can call display. getcurrent () to check whether the current thread is a UI thread. If display. getcurrent () returns NULL, it indicates that the current thread is not a UI thread.
Typical Cases of using threads in eclipse
- Control concurrent job running
For some jobs, in order to avoid concurrency problems, we hope that only one such job is running at the same time. In this case, we need to control the concurrent running of the job. In another case, we also need to control the concurrent running of jobs: for a job in the program, we may start a job for execution. for a small number of tasks, this is feasible, but if we predict that there may be a large number of tasks at the same time, if each task starts a job, we will start a lot of jobs at the same time. These jobs occupy a large amount of resources and affect the execution of other jobs. We can use the job rule to control concurrent job execution. The following code is simple. First, we define the following rule:
Private ischedulingrule schedule_rule = new ischedulingrule () {public Boolean contains (ischedulingrule rule) {return this. equals (rule);} public Boolean isconflicting (ischedulingrule rule) {return this. equals (rule );}}; |
For jobs that need to be avoided running at the same time, we can set their rule to the rule defined above. For example:
Myjob1.setrule (schedule_rule); myjob2.setrule (schedule_rule ); |
In this way, myjob1 and myjob2 jobs are not executed at the same time. Myjob2 will wait until myjob1 is executed. This is implemented by eclipse's jobmanager. Jobmanager ensures that the rule of any two started jobs does not conflict. The rule we defined above is the simplest. We can rewrite the isconflicting function to implement more complex control. For example, we can control that only a specified number of jobs of the same type can run at most. However, we should note that the isconflicting method cannot be too complex. Once the rule of a job conflicts with the rule of other jobs, the isconflicting method is called many times. If the calculation is too complex, the overall performance will be affected.
Some of our jobs may not be executed immediately. In some cases, when the job is ready for execution, the task to be executed by the job is meaningless. In this case, we can use shouldschedule () and shouldrun () of the job to avoid running the job. When defining a job, we can reload the shouldschedule and shouldrun methods. In these methods, we can check the prerequisites for running a job. If these conditions are not met, we can return false. During job scheduling, jobmanager first calls the shouldschedule method of the job. If the returned value is false, jobmanager no longer schedules the job to run. Similarly, before a thread is actually started to run a job, jobmanager calls the shouldrun method of the job. If false is returned, jobmanager no longer runs the job. In the following example, we want to start a job and update the content in the text box after 10 seconds. To ensure that our job is meaningful during running, we need to ensure that the text box we want to update is not destroyed. We have reloaded the shouldschedule and shouldrun methods.
Text text = new text (parent, SWT. none); uijob refreshjob = new uijob ("Update interface") {public istatus runinuithread (iprogressmonitor Monitor) {text. settext ("new text"); Return status. OK _status;} public Boolean shouldschedule () {return! Text. isdisposed ();} public Boolean shouldrun () {return! Text. isdisposed () ;}}; refreshjob. Schedule (10000 ); |
- Tasks that involve long processing in the UI thread
We often encounter a situation where a user's menu or button triggers a query of a large amount of data. After the data is queried, the user updates the table and other interface elements. The handler triggered by a user clicking a menu or button is generally in the UI thread. To avoid blocking the UI, we must put time-consuming tasks such as data query in a separate job for execution, once the data query is complete, we must update the interface. At this time, we need to use the UI thread for processing. The following is an example of code to handle this situation:
Button. addselectionlistener (New selectionlistener () {public void widgetselected (selectionevent e) {perform ();} public void widgetdefadefaselected (selectionevent e) {perform ();} private void perform () {job = new job ("Get Data") {protected istatus run (iprogressmonitor Monitor) {// Add the code display for getting data here. getdefault (). asyncexec (New runnable () {public void run () {// code for adding an update interface here}) ;}; job. schedule ();}}); |
- Delayed job execution to avoid useless job execution
We often need to refresh some of our interface elements based on the selected object. If we change the selection quickly, and each time the page is refreshed, the page will flash. From the user's point of view, we can quickly change the selection. What we hope to see is the final selection result, and refreshing the interface in the middle is unnecessary.
In jface, structuredviewer provides the addpostselectionchangedlistener method. If we use this method to listen to the selectionchanged event, when the user keeps pressing the direction key to change the selected, we will only receive one selectionchanged event. In this way, we can avoid over-refreshing the interface.
In fact, jface implements this function by executing jobs in a delayed manner. We can also implement similar functions by ourselves:
Private final static object update_ui_jobfamily = new object (); tableviewer. addselectionchangedlistener (New iselectionchangedlistener () {public void selectionchanged (selectionchangedevent event) {job. getjobmanager (). cancel (update_ui_jobfamily); New uijob ("Update interface") {protected istatus runinuithread (iprogressmonitor Monitor) {// update the return status of the interface. OK _status;} public Boolean belonsto (Object family) {return family = update_ui_jobfamily ;}}. schedule (500 );}}); |
First, we need to put the code updated on the interface into a uijob. At the same time, we need to delay the job execution by 500 milliseconds (we can change the delay time as needed ). If the next selectionchanged event comes soon, our call job. getjobmanager (). Cancel (update_ui_jobfamily) will cancel the previous unexecuted job, so that only the last job will actually run.
- Wait for the end of a non-UI thread in the UI thread
Sometimes, in the UI thread, we need to wait for a non-UI thread to finish executing. For example, we want to display some data in the UI thread, but the data needs to be obtained from the database or remote network. Therefore, we will start a non-UI thread to obtain data. Our UI thread must wait until the execution of this non-UI thread is completed before it can continue to be executed. Of course, a simple implementation method is to use join. We can call the join method of a non-UI thread in the UI thread, so that we can wait until it is finished and continue. However, there is a problem. When our UI thread waits, it means that our program will not respond to interface operations or refresh. In this way, the user will feel that our program is as unresponsive as it is dead. In this case, we can use the modalcontext class. You can run the task to obtain data using the run method of modalcontext (as follows ). Modalcontext puts your task in an independent non-UI thread for execution, and waits until it is executed. Unlike the join method, modalcontext does not stop processing UI events while waiting. In this way, our program will not respond.
Try {modalcontext. run (New irunnablewithprogress () {public void run (iprogressmonitor Monitor) throws invocationtargetexception, interruptedexception {/* code to be executed in a non-UI thread */modalcontext. checkcanceled (MONITOR) ;}}, true, new nullprogressmonitor (), display. getcurrent ();} catch (invocationtargetexception e) {} catch (interruptedexception e ){} |
- Uniformly process associated jobs
Sometimes, we need to process associated jobs together. For example, you need to cancel these jobs at the same time or wait until all these jobs end. In this case, we can use job family. For associated jobs, we can set them to the same job family. We need to reload the belonsto method of the job to set the job family of a job.
Private object my_job_family = new object (); job = new job ("job name") {protected istatus run (iprogressmonitor Monitor) {// Add your task code return status here. OK _status;} public Boolean belonsto (Object family) {return my_job_family.equals (family );}}; |
We can use a series of jobmanager methods to perform operations on the job family:
Job. getjobmanager (). cancel (my_job_family); // cancel all jobjobs belonging to my_job_family. getjobmanager (). join (my_job_family); // wait until all jobs of my_job_family end the job. getjobmanager (). sleep (my_job_family); // transfers all jobs belonging to my_job_family to the sleep status job. getjobmanager (). wakeup (my_job_family); // wake up all jobs belonging to my_job_family |
Debugging and troubleshooting of thread deadlocks
Once we use a thread, there may be deadlocks in our program. In the event of a deadlock, the deadlocked thread will not respond, resulting in a decline in program performance. If a deadlock occurs in our UI thread, our program will not respond and must restart the program. Therefore, in our multi-threaded program development, it is extremely important to discover deadlocks and solve the deadlock problem to improve the stability and performance of our program.
If we find that the program runs abnormally (for example, the program has no response), we must first determine whether a deadlock has occurred. Through the following steps, we can determine whether the deadlock and the deadlock thread:
- Run the program in debug mode in eclipse
- Execute the response test case to reproduce the problem
- In the debug view of Eclipse, select the main thread (thread [main]) and choose run> suspend. At this time, eclipse expands the function call stack of the main thread, and we can see the operations being performed by the current main thread.
- Normally, eclipse is waiting for user operations, and its function call stack will be similar to the following:
Image example
- If a deadlock occurs in the main thread, the top layer of the function call stack is generally your own function call. You can check your current function call to determine what the main thread is waiting.
- Use the same method to view other threads, especially those waiting for the UI thread.
We need to find out the waiting relationship between the current thread to find out the cause of the deadlock. After finding the deadlock thread, we can handle it in different situations:
- Reduce lock granularity and increase concurrency
- Adjust the order of resource requests
- Place the task waiting for resources in an independent thread for execution.
Precautions during job usage
- do not use the thread. Sleep method in the job. If you want to put the job into sleep state, you 'd better use the sleep method of the job. Although the effects of thread. Sleep and job sleep methods are similar, their implementation methods are completely different and their impact on the system is also different. We know that jobs in eclipse are managed by jobmanager of Eclipse. If we call the sleep method of the job, jobmanager transfers the job to sleep state, and the corresponding thread will re-enter the thread pool to wait for other jobs to run. If we call the thread. Sleep method directly in the job, it will directly bring the thread running the job into sleep state, and other jobs will not be able to reuse this thread. At the same time, although the thread running the job enters the sleep state, the job status is still running, and we cannot wake up the job using the wakeup method of the job.
- Job cancellation. In general, we intuitively think that once the Cancel Method of the job is called, the job will stop running. In fact, this is not necessarily true. When the job is in different states, the effect of calling the Cancel Method of the job is different. When a job is in the waiting and sleeping statuses, once the cancel method is called, jobmanager will delete the job from the queue waiting for running, and the job will not run again, the cancel method returns true. However, if the job is running, the cancel method call does not immediately terminate the job. It only sets a flag indicating that the job has been canceled. We can use the iprogressmonitor monitor parameter passed in by the run method of the job. The iscanceled method of this parameter will return whether the job is canceled. If necessary, we must check whether the job is canceled in the proper position of our code and make appropriate responses. In addition, because the Cancel Method of the job cannot be called to terminate the job immediately, if we need to wait for the canceled job to run and then execute it, we can use the following code:
If (! Job. Cancel () job. Join (); |
-
- Use of the join method. The join method will cause one thread to wait for another thread. Once the waiting thread has a lock required by the waiting thread, a deadlock will occur. When synchronization is required in our threads, this deadlock situation is very prone, so we must be very careful when using join and try to replace it with other methods.
- Avoid errors caused by outdated jobs. Since the thread we start is not necessarily executed immediately, the situation may change when our job starts to run. We need to consider these situations in the job processing code. A typical case is that when we start a dialog box or initialize a viewpart, we start some jobs to read some data. Once the Data Reading is complete, we will start a new UI job to update the corresponding UI. Sometimes, the dialog box or view is closed immediately after a dialog box or view is opened. At this time, the thread we started is not interrupted. Once the UI is updated in the job, an error will occur. In our code, we must handle it accordingly. Therefore, before updating the interface elements in the thread, we must first check whether the corresponding control has been dispose.
Conclusion
When we develop an eclipse-based client, using multithreading can greatly provide our program concurrent processing capabilities, while also helping to improve user experience. However, multi-threaded programs also have their disadvantages. We should not abuse the thread:
- First, the multi-threaded program will greatly increase the complexity of our program, making our development and debugging more difficult.
- Second, too many threads are prone to deadlock, data synchronization, and other concurrency problems.
- In addition, because thread creation and destruction require overhead, the overall performance of the program may be reduced due to the use of multiple threads.
Therefore, we must be cautious when using threads. This article discusses eclipse threads and hopes to help you use them. As the actual situation is complex, the methods mentioned in this article are for reference only. Readers need to analyze different actual problems to find the best solution.
References
- View the latest information on the developerworks blog.
About the author
Liang Yu, IBM China software development center, is now engaged in the development of workplace managed client software, through liangq@cn.ibm.com can contact him.
Reprinted from: http://www.ibm.com/developerworks/cn/opensource/os-cn-eclipse-multithrd/