Realize the background process in Visual Basic .NET

xiaoxiao2021-03-06  22

Rockford Lhotka

MAGENIC TECHNOLOGIES

October 1, 2002

Download the VBBackground.exe sample file from the MSDN Code Center. (Note that in the sample file, the programmer's comment is English, which is translated into Chinese to make it easy for the reader.)

Summary: Rocky Lhotka recommends a structured architecture example, which can be used to act as a medium between the auxiliary threads and the UI threads, simplifying the process of writing multi-threaded auxiliary code and UI to control it. This architecture includes a downloadable code example that can be adjusted according to your application.

With multi-threading, you can make the application simultaneously perform multiple tasks. With multi-threading, you can run a thread to run the user interface, allowing another thread to perform complex operations or operate in the background. Since Microsoft® Visual Basic® .NET supports multithreading, we can easily get this feature.

But multithreading also has its shortcomings. When the application uses multiple threads, we will always encounter such problems: Multiple threads at the same time attempt to interact with the same data or resources. When this happens, the problem will become very complicated and difficult to debug.

Worse, multi-threaded code usually seems to be operating normally during the initial development, but during production, due to unexpected conditions (multiple threads interact with the same data or resources), it will fail. This increases the risk of multi-threaded programming!

Due to design and debug multi-threaded applications are very difficult, Microsoft creates a "single-line unit" (STA) concept in COM. The Visual Basic 6 code is always running in STA, so the code only needs to consider single-thread. This will thoroughly avoid the problems caused by sharing data or resources, but it also means that we must take strict measures to take advantage of multi-threaded.

This common problem in STA will not occur in .NET. All .NET code is running in AppDomain that allows multiple thread operations. This means that the Visual Basic .NET code is also run in AppDomain, so you can use multiple threaded operations. Obviously, this will be carefully written in any time to avoid conflicts between threads.

To avoid conflicts between threads, the easiest way is to ensure that multiple threads will never interact with the same data or resources. Although it is unlikely, for any multi-threaded application, you should use or use less shared data or shared resources when designing or use less.

This not only simplifies the encoding and debugging process, but also improves performance. To resolve the conflict between the threads, you must use synchronization techniques that block or suspend other threads prior to completing an operation of a thread. Blocking threads is to make the thread are idle, and do not perform any operations, thereby reducing performance.

Cancel button and status display

There are a variety of reasons for using multiple threads in applications, but the most common reason is that we need to perform a long-running task, and on the other hand, it is hoped that some or all user interfaces are always in response state. .

At least we should keep the Cancel button to keep a response state, allowing users to tell the system through it, they want to terminate long-running tasks.

In Visual Basic 6, we tried to use the doevents, timer controls, and many other methods. The operation in Visual Basic .NET is much simpler because we can use multiple threads. Moreover, as long as we are careful, you can do this and do not complicate code or debugging.

To successfully implement the Cancel button in a multi-threaded environment, the key is to remember the Cancel (button) just "request" to cancel the task. When the task is determined by the ensemble. If we implements a Cancel button that can directly stop the background process, you may stop in the process of performing some sensitive operations, or close important resources (for example, file handler or database connections) during the background process (for example, file handler or database connections) It stops before. This is likely to lead to serious consequences, causing crash, application behavior instability, or application is completely collapsed.

Therefore, the role of the Cancel button should only request to stop the background task. The background task can check if there is a request for cancel operation at a time point. If a request for cancel operation is detected, the background thread can release all resources and stop all important operations and termination normally.

Although the request cancel operation is very important, we prefer to be able to display status information of the background process via the UI. The status information can be a message in a text format or a percentage of completion tasks, or two messages simultaneously.

To implement the Cancel button or status display in Visual Basic .NET, the most complex issue we face is that the Windows Form Library is not unsecured with threads. This means that only the thread that creates a form can be interacted with the form or its control. Other threads can not be interactively interact with the form or its control.

However, we cannot avoid writing multithreading and a code that interacts with a given form. Therefore, it may generate unpredictable consequences when runtime, and may even cause the application to crash.

This requires us to be cautious when you encode, but also make sure that only our UI thread is interacting with the UI. To do this, we can create a simple architecture, managing the interaction between the background auxiliary thread and the UI thread. If possible, you can make the code of the UI code and the scheduled task relatively clearly to we are using multithreading.

Threads and objects

If you want to create a background process and allow it to use its own data to run on its own thread, the easiest way is to create objects specifically for the background process. Although not necessarily achieve, it is a positive goal because it greatly simplifies the creation process of multi-threaded applications.

If the background thread runs in its own object, the rear tape can use the instance variable of the object (variable declared in the class) without worrying that these variables are used by other threads. For example, consider the following classes:

Public Class Worker

Private Minner AS Integer

Private mouter as in

Public Sub New (Byval Innersize As Integer), ByVal OUTERSIZE AS INTEGER

Minner = InnerSize

mouter = OUTERSIZE

End Sub

Public SUB WORK ()

Dim InnerIndex as inner

DIM OTERINDEX AS INTEGER

DIM Value As Double

For OuterIndex = 0 to mouter

For InnerIndex = 0 to Minner

'Do Some Cool Calculation Here

Value = math.sqrt (CDBL (InnerIndex - OuterIndex))

NEXT

NEXT

End Sub

END CLASS

This class is suitable for running in the background thread and can be started using the following code:

Dim MyWorker AS New Worker (10000000, 10)

Dim Backthread As New Thread (Addressof MyWorker.work) backthread.start ()

The instance variable in the Worker class can store its data. The background thread can safely use these variables (Minner and Mouter), and ensure that other threads do not access these variables at the same time.

We can use any starting data to initialize the object with any starting data. Before actually starting the background thread, our primary application code creates an instance of this object and initializes the data to be operated using the background thread.

The background thread will acquire the address of the Work method of the object and start start. This thread will immediately run the code inside the object and use the dedicated data of the object.

Since the object is self-contained, we can create multiple objects, each object running on its own thread and is relatively independent.

However, this implementation is not ideal. The UI cannot obtain the status of the background process. We have not implemented any mechanisms, so that the UI can request to terminate the background process.

To solve the above two problems, the background thread and the UI thread need to interact in some way. This interaction is very complicated, so it is best to put interact in a class in some way, so that the UI and the auxiliary code do not have to worry about details.

Architecture

We can create an architecture that makes the UI and auxiliary code do not need to perform threaded interaction. In fact, we can achieve this goal, and can implement an architecture that can implement complex code in some way, can be used to manage or control the background threads and their UI interactions.

Let's first discuss the architecture and then discuss how to design and implement code. This code can be downloaded from the related links herein and describes how to use this code.

Typically, a single thread will be initiated first in the application to open the user interface. We named it "UI Thread" for understanding. "UI Thread" is a unique thread in many applications, so it is to handle the UI and complete all operations.

However, now we create a "secondary thread" to perform some background operations, allowing the UI thread toggle processing the user interface. This makes it possible to maintain a response to the user even if the auxiliary thread is busy.

We insert a layer of code between the UI thread and the auxiliary thread to act as an interface between the UI and the auxiliary code. This code is essentially a "controller" to manage and control the interaction between the auxiliary thread and its interaction with the UI.

Figure 1: UI thread, controller and auxiliary thread

The code containing the controller securely initiates the auxiliary thread, transfer any status message from the secondary thread to the UI thread, and transfer any cancel request from the UI thread to the secondary thread. The UI code and auxiliary code cannot be interacted directly, and they are usually interacting through the controller's code.

However, except for the auxiliary thread is activated "previous" and "after", the UI code can interact with the WORKER object. Before starting the auxiliary thread, the UI can create and initialize the Worker object. After terminating the auxiliary thread, the UI can retrieve any value from the Worker object. From the perspective of the UI, the following event stream will be formed:

Create a Worker object. Initialize the Worker object. Call the Controller to initiate a secondary thread.

The Worker object will send status information to the UI via Controller. The UI can send the cancel request to the Worker object via Controller. The Worker object notifies the UI after the operation is completed. Values ​​can be retrieved from the Worker object.

In addition to the restrictions of the UI code that the UI code cannot directly interact with the Worker object when the auxiliary thread is activated. The UI will remain activated and responded to the user even if the background operation is running.

From the perspective of the Worker object, the following event stream will be formed:

The UI code creates a Worker object. The UI code uses the required data to initialize the Worker object. Controller Creates a method of latter threads and calling the Worker object. The Worker object runs the auxiliary code. The Worker object passes status information to the Controller so that the controller will transfer the status information to the UI. The Worker object checks if there is a cancel request if there is a cancellation request. If there is, it stops running. After the Worker object is complete, tell the controller to be completed so that Controller will transfer the information to the UI. The auxiliary thread is now terminated, so the UI can interact directly with the Worker object.

Since the auxiliary code is only interacting with the Controller, we don't have to worry about the interaction with the UI components (this will undoubtedly make the application unstable). Now, the auxiliary code rely on Controller to properly communicate with the UI thread, so the operations are safe.

This means that as long as the instance variable in the Worker object is handled, there is no need to handle any thread problems in the auxiliary code.

Using charts can usually understand interactions between different components (especially components on different threads). Microsoft® Visio® supports Creating a UML (General Modeling Language) chart, which is very helpful for understanding.

The following is a UML sequence chart for the UI, Worker object, and Controller. This chart assumes that there is no cancellation request. The Worker and Controller objects overlap the vertical activity columns on the vertical line highlight the code running on the auxiliary thread. All other code run on the UI thread.

Figure 2: Description Procedure Sequence Chart

You can also view the event stream using the UML activity chart. This chart has a focus on tasks instead of objects, thereby displaying a series of steps that occur, and flow between steps. We can easily see how the UI code stays in the thread on the left, and how the Worker object works on the thread on the right. The Worker object can be used directly from the UI before running in other threads to initialize the value, and then retrieve the result.

Figure 3: Activity chart showing process flow

Using such a chart can help us find that the UI is inactive when the backend thread is activated, and the UI is inadvertently interacting. Any such interaction requires additional encoding to avoid errors that may unstable applications. In the ideal state, this interaction is implemented through the Controller component, and we can include all encodings to make interactive security.

The figure below illustrates the event sequence when the UI issues cancels the request.

Figure 4: Sequence chart showing the cancel request

Note that the cancel request is sent from the UI to the Controller, then the Worker thread is verified with the Controller, and the cancel request has occurred. Both UI and Controller do not force the auxiliary code to terminate, but allows the auxiliary code to be normally stopped.

Architecture design

To achieve the behavior we discussed, it is clear that the Controller class is required. In order for this architecture to be applied in most scenarios, we will also define some official interfaces, which can be used by Controller when interacting with the UI (or client) and the auxiliary thread.

By defining a formal interface for the client and auxiliary thread, we can use the same Controller object in different situations, and you can use different UI requirements and different Worker objects as needed.

The following UML class chart displays the Controller class and the iClient and iWorker interfaces. It also shows the icontroller interface, and the auxiliary code will interact with the Controller object. Figure 5: Class chart for Controller and related interfaces

The method of the iClient interface definition will be called by the Controller object to communicate the start time, end time, and any intermediate status message to the client UI. It also contains a method indicating a secondary code failure.

In most cases, we can implement these methods as an event that is issued by the Controller object and processed by the UI. However, from the auxiliary thread, the event is then properly handled by the UI thread is not easy, so we implement it as a group of methods.

Make the controller code (running on the auxiliary code) to call these methods in the UI and processed by the UI thread, so relatively much more simple.

Similarly, the iWorker interface defines a method that is called by the Controller object, allowing it to interact with the auxiliary code. Using the initialize method can provide a reference to the Controller object for the secondary code, and the use of the START method can start the operation on the background thread.

START methods cannot contain any parameters due to thread work mode. When starting a new thread, the address of the method that does not accept any parameters must be passed to the thread.

Note that there is no Cancel or STOP method in the iWorker interface. We cannot force the auxiliary code to stop, and there is no need; but the auxiliary code can use the IController interface to ask if the Controller object has cancellation requests.

The iController interface defines the method that the auxiliary code can be called on the Controller object. It allows the auxiliary code to check the Running flag. If there is a cancel request, the Running flag is false. It also allows the auxiliary code to tell Controller when the work is completed or cannot be completed, and allows the status message and completing the percentage value (Integer between 0 and 100) update Controller.

Finally we define the Controller object. This object contains some methods that can be called by the UI code. These include START methods that can start the background operation by providing the Controller object to the reference to the Worker object. Also included in the Cancel method, the method is used to request cancel operation. The UI can also check the running property to see if there is a cancellation request; you can also check the percent property to see the percentage of the task.

The constructor method contained in the Controller class accepts iClient as a parameter, allowing the UI to provide reference to the form (for handling messages in Worker).

In order to implement a series of animation points to display thread activities, we will create a simple Windows Form Control that uses the timer to change the color in a series of PictureBox controls.

Implementation

We will implement this architecture in the Class Library project, allowing it to run the application of the background process.

Open Visual Studio .NET and create a new Class Library application called Background. Since this library will include Windows Form Controls and Forms, you need to use the Add References dialog to reference System.Windows.drawing.dll. In addition, we can import namespaces within these projects using the project's property dialog, as shown in Figure 6. Figure 6: Adding a project space IMPORTS

Once this is done, you can start coding. Let us start with the creation interface.

Define interface

Add a class in an item named iClient, and replace it with the following code:

Public interface iClient

Sub Start (ByVal Controller AS Controller)

Sub Display (ByVal Text As String)

Sub Failed (ByVal E AS Exception)

Sub Completed (Byval Cancelled As Boolean)

End interface

Then add the class named IWORKER and replace it with the following code:

Public Interface IWORKER

Sub Initialize (ByVal Controller As Icontroller)

SUB Start ()

End interface

Finally, the class named Icontroller is added, the code is as follows:

Public Interface Icontroller

Readonly property Running () As Boolean

Sub Display (ByVal Text As String)

Sub setpercent (ByVal Percent as integer)

Sub Failed (ByVal E AS Exception)

Sub Completed (Byval Cancelled As Boolean)

End interface

At this point, we have defined the interfaces in all class charts described earlier as described herein. You can now implement the Controller class.

Controller class

Now we can realize the core of the architecture, the Controller class. The code contained in this class can be used to initiate a secondary thread, as well as the medium between the UI thread and the auxiliary thread prior to completion of the auxiliary thread.

Add a new class in a project called Controller. First add imports, and declare some variables:

Imports system.threading

Public Class Controller

Implements Icontroller

Private MWORKER AS IWORKER

Private Mclient as Form

Private mrunning as boolean

Private MPERCENT AS INTEGER

Then you need to declare some delegate. The entrustment is the formal pointer of the method, and the entrustment of the method must have the same method signature as the method itself (parameter type, etc.).

The purpose of the commission is very wide. In our example, commission is very important because it entrusts us to make a way to call the form to run on the UI thread of the form. As ICLIT is defined, three methods to be called on the form require a delegation:

'This entrustment signature and iClient.completed

'The signature in' is matched and used for safe

'Method on the UI thread

Private delegate subpleteddelegate (byval canellled as boolean) 'This entrustment signature with iClient.display

'The signature in' is matched and used for safe

'Method on the UI thread

Private Delegate Sub DisplayDelegate (ByVal Text As String)

'This entrustment signature with iClient.Failed

'The signature in' is matched and used for safe

'Method on the UI thread

Private delegate Sub FaileddeLegate (Byval E as Exception)

IClient also defines a Start method, but this method can be called from the UI thread, so it is not necessary to delegate.

The code that will be called from the UI thread will be written. The code includes the constructor method, the Start, and the Cancel method, and the Percent property. I put these contents in Region and make it clear that they are calling from the UI thread.

#Region "Code from the UI thread call"

'Initializing Controller using the client

Public Sub New (Byval Client As Iclient)

Mclient = CType (Client, Form)

End Sub

'This method is called by UI, so

The 'UI thread is running. We will

'Start auxiliary thread

Public Sub Start (Optional Byval Worker as IWorker = Nothing)

'If the auxiliary thread has started, an error will occur.

IF mrunning then

Throw New Exception ("Background Process Already Running")

END IF

Mrunning = true

'Store reference to the auxiliary object, and

'Initializing the auxiliary object, make it included

'References to Controller

MWORKER = WORKER

MWORKER.INITIALIZE (ME)

'Creating a background thread

'For background operation

DIM Backthread As New Thread (Addressof MWORKER.START)

'Start background work

Backthread.Start ()

'Tell the client background work has started

Ctype (mclient, iclient) .start (me)

End Sub

'This code is called by UI, so in UI

'Run on the thread. It only sets the request

'Canceled sign

Public Sub Cancel ()

Mrunning = false

End Sub

'Return to complete the percentage value, and

'Is only called by UI thread

Public Readonly Property Percent () AS INTEGER

Get

Return MPERCENT

END GET

End Property

#End region

The only special code here is in the START method, we can create a secondary thread in this method and start the thread:

DIM Backthread As New Thread (Addressof MWORKER.START)

Backthread.Start ()

To create a thread, you need to pass the address of the Start method on the iWorker interface of the Worker object. Then, simply call the START method of the thread object to start operation. At this time we have to pay special attention, the UI should not interact directly with the Worker, and Worker should not interact with the UI. Note that the CANCEL method only sets a flag, indicating that we don't want to continue running. The auxiliary code should be viewed regularly to determine if it should be stopped.

Now, we can implement the code that will be called by the secondary thread when the Worker object is run. This code is interesting because it must transfer Display and Completed from the secondary thread to the UI thread, but also do this on the UI thread.

To do this, we can use the INVOKE method for the Form object. This method accepts the promise pointer to the method that the form should be called, and an Object type array containing the parameters of the method.

The Invoke method does not directly call the method on the form, but the request form returns and uses the UI thread using the form to call the method. This operation can be completed in the background by sending a Windows message to the form. This indicates that the form obtains these method calls in the same way as the Click or KeyPress event from the operating system is basically the same.

Usually, these details will not affect the overall situation. The result is triggered by the Invoke method, and the method of running it on the UI thread is terminated by the process form, which is the goal we want to implement.

Reesence again, this code is in Region, the purpose is to explicit it will call on the auxiliary thread:

#Region "code from the auxiliary thread"

'Call from auxiliary thread to update

'This will trigger the UI that contains status text.

'Method call - this call is on the UI thread

' ongoing

Private sub Display (byval text as string) _

Implements icontroller.display

DIM DISP AS New DisplayDelegate

Addressof CType (Mclient, Iclient) .display)

DIM ar () as object = {text}

'Call the client form on the UI thread

'Displaying

Mclient.BeginInvoke (DISP, AR)

End Sub

'From the auxiliary thread to indicate a failure

'This will trigger the UI containing an exception object.

'Method call - this call is on the UI thread

' ongoing

Private sub failed (Byval E as Exception)

Implements icontroller.failed

DIM DISN NEW FAILDELEGATE (_

Addressof ctype (mclient, iclient) .failed)

DIM AR () as object = {E}

'Call the client form on the UI thread

'To indicate failure

Mclient.invoke (DISP, AR)

End Sub

'Call from the auxiliary thread to point to the percentage of completion

'The value will go to Controller, read by the UI when needed

Private sub setpercent (Byval Percent as integer)

Implements Icontroller.SetPercent

MPERCENT = percent

End Sub

'Call from auxiliary thread to indicate that has been completed

'We also pass parameters to indicate whether it is really completed.

'And whether to cancel the call to UI' on the UI thread

Private subpleted (byval canellled as boolean)

Implements icontroller.completed

Mrunning = false

DIM Comp as new completeddelegate (_

Addressof CType (Mclient, Iclient) .completed)

DIM AR () as object = {cancelled}

'Call the client form on the UI thread

'To indicate that has been completed

Mclient.invoke (CoMP, AR)

End Sub

'Indicates whether it is still running or canceled?

'This will call on the auxiliary thread, so

'Auxiliary code can check if it should be normal

' drop out

Private readonly property running () as boolean _

Implements icontroller.running

Get

Return Mrunning

END GET

End Property

#End region

Failed and Completed methods utilize the INVOKE method of the form. For example, a failed method can do the following:

DIM DISN NEW FAILDELEGATE (_

Addressof ctype (mclient, iclient) .failed)

DIM AR () as object = {E}

'Call the client form on the UI thread

'To indicate failure

Mclient.invoke (DISP, AR)

First create a failed method to the client form from the iClient interface. Then declare the Object type array containing the parameter value to the method. Finally, the INVOKE method of the client form is called, and the delegate pointer and parameter array are passed to the form.

The form will use these parameters to call this method on the UI thread (form where the form can be safely run safely in update).

The entire process is in synchronous, that is, the secondary thread will stop when the form is called. Although the auxiliary thread can be stopped when the error message is displayed or the message is displayed, we do not want to display the auxiliary thread when each small state is displayed.

To avoid the display state, stop the auxiliary thread, and the Display method will use BeginInvoke without using invoke. BeGinInvoke makes the method on the form to communicate asynchronously, so that the auxiliary thread can remain in operation, do not need to wait for the display method on the form:

DIM DISP AS New DisplayDelegate

Addressof CType (Mclient, Iclient) .display)

DIM ar () as object = {text}

'Call the client form on the UI thread

'Displaying

Mclient.BeginInvoke (DISP, AR)

Use BeGinInvoke in this way to prevent the auxiliary threads from stopping, enabling the auxiliary thread with as high performance as possible.

ActivityBar control

Finally, let's create an ActivityBar control showing an animated point.

Add a user control in an item named ActivityBar.

The width of the control is adjusted to about 110, and the height is adjusted to about 20. You can adjust the boundary, or by setting the size property in the Properties window.

The rest of the operation will be done through the code. To create a series of animation "lights" that is not flashing, you can use a series of PictureBox controls with the Timer control. Each time the Timer control is turned off, we will make the next Picturebox green, and the PictureBox that has been displayed green is changed to the background color of the form. Place the Timer control in the Windows Forms tab into the form and change its name to Tmanim. At the same time, set the Interval property to 300 to achieve a better animation speed.

By the way, there is a different TIMER control in the Components tab. It is a multi-thread timer. That is, the timer will raise the ELAPSED event in the background thread, not an ELAPSED event on the UI thread like the Windows Form Timer. This method usually produces the opposite effect when establishing a UI because the code in the ELAPSED event clearly cannot interact directly with our UI.

Now add the following code to the control:

Private mboxes as new arrivallist ()

Private mcount as integer

Private sub activitybar_load (byval sender as system.object, _

BYVAL E as system.eventargs) Handles mybase.load

DIM INDEX AS INTEGER

IF mboxes.count = 0 THEN

For index = 0 TO 6

Mboxes.Add (CreateBox (INDEX))

NEXT

END IF

Mcount = 0

End Sub

Private Function CreterBox (Byval Index As Integer) AS PictureBox

DIM BOX As New Picturebox ()

WITH BOX

SetPosition (Box, INDEX)

.Borderstyle = borderstyle.fixed3d

.Parent = me

.Visible = TRUE

End with

Return Box

END FUNCTION

Private sub graydisplay ()

DIM INDEX AS INTEGER

For index = 0 TO 6

CType (Mboxes (Index), PictureBox .BackColor = Me.Backcolor

NEXT

End Sub

Private sub setPosition (Byval Box As Picturebox, Byval Index As Integer)

DIM LEFT AS INTEGER = CINT (Me.width / 2 - 7 * 14/2)

DIM TOP AS INTEGER = CINT (Me.Height / 2 - 5)

WITH BOX

.Height = 10

.Width = 10

.Top = TOP

.Lef = left index * 14

End with

End Sub

Private subtmanim_tick (byval sender as system.Object, _

Byval e as system.eventargs) Handles Tmanim.Tick

CType (MboXes (McOUNT 1) MOD 7), PictureBox) .Backcolor = _

Color.LightGreenctype (Mboxes (MPVNT MOD 7), PictureBox) .BackColor = Me.BackColor

McOUNT = 1

IF McOUNT> 6 Then Mcount = 0

End Sub

Public Sub Start ()

CType (Mboxes (0), PictureBox). Before = color.lightgreen

Tmanim.enabled = TRUE

End Sub

Public Sub [stop] ()

Tmanim.enabled = false

Graydisplay ()

End Sub

PRIVATE SUB ACTIVITYBAR_RESIZE (Byval Sender As Object, _

BYVAL E as system.eventargs) Handles mybase.resize

DIM INDEX AS INTEGER

For index = 0 to mboxes.count - 1

SetPosition (CType (INDES (INDEEX), Picturebox, Index

NEXT

End Sub

The Load event of the form creates a PictureBox control and puts them in array so that we can loop between them. The TIMER control Tick event loop display makes each control in turn green.

All operations start from the Start method and end by the STOP event. Since STOP is a reserved word, put this method in square brackets: [STOP]. The STOP method not only stops the timer, but also grasses all boxes to tell the user that there is currently no activity in these boxes.

Create a Worker class

The Worker class has been briefly introduced in front of this article. Because we have defined the iWorker interface, you can enhance this class to use the Controller we created.

First create a background.dll file. This step is important because if this step is not completed, the ActivityBar control will not be displayed on the toolbox when we establish a test form.

Add a Windows Forms Application called BGTEST in the solution. Use the right-click on the item and select the appropriate menu item in the Solution Explorer and select the program to start the project.

Then use the Projects tab in the Add References dialog box to add a reference to the Background project.

Now add a class in a project called Worker. Some of the code is the same as the code described in the previous, but it also contains some different code to implement the iWorker interface (some of which highlights some of the display):

Imports background

Public Class Worker

Implements IWORKER

Private MController as icontroller

Private Minner AS Integer

Private mouter as in

Public Sub New (Byval Innersize As Integer), ByVal OUTERSIZE AS INTEGER

Minner = InnerSize

mouter = OUTERSIZE

End Sub

'Calling by Controller to get

'Controller's reference

Private subinit (Byval Controller As Icontroller) _IMplements IWORKER.INITIALIZE

MController = Controller

End Sub

Private Sub Work () IMPLEments IWORKER.START

Dim InnerIndex as inner

DIM OTERINDEX AS INTEGER

DIM Value As Double

Try

For OuterIndex = 0 to mouter

If MController.Running Then

MController.display ("Outer Loop" & OuterIndex & "Starting")

MController.SetPercent (Cint (OuterIndex / Mouter * 100))

Else

'They request cancellation

MController.completed (True)

EXIT SUB

END IF

For InnerIndex = 0 to Minner

'Do some interesting calculations here

Value = math.sqrt (CDBL (InnerIndex - OuterIndex))

NEXT

NEXT

MController.setpercent (100)

MController.completed (false)

Catch e as exception

MController.failed (e)

END TRY

End Sub

END CLASS

We add init methods capable of implementing iWorker.Initialize. Controller will call this method, so we can reference the Controller object later.

We also change the Work method to Private, just to implement iWorker.Start methods. This method will run on the auxiliary thread.

We enhance the Work method to make it use TRY..CATCH blocks. This allows us to capture any errors using the FAILED method on the Controller and return it to the UI.

Suppose the code is running, we call the Display and SetPercent methods of the Controller object so that they update their status and completion as the code runs.

We also check the Running property of the Controller object regularly to see if there is a cancellation request. If there is a cancel request, stop the process and indicate that the operation is stopped due to the cancel request.

Create a displayed form

Finally, we can create a form that uses it to start or cancel the background process. The form will also display activity and status information.

Open the Form1 designer and add two buttons (BTNSTART and BTNREQUESTCANCEL), two tags (Label1 and Label2), a progressbar (programsbar1), and an ActivityBar (ActivityBar1), as shown in Figure 7.

Figure 7: Form1 control layout

This form needs to implement iClient so that the Controller object is interactive:

Imports background

Public Class Form1

Inherits System.Windows.Forms.form

Implements iClient

The form also requires a Controller object and a flag to track the background operation is active or in the completion state.

Private MController As New Controller (ME)

Private Mactive As Boolean then, we can add methods to implement interfaces defined by iClient. It is recommended to place these methods in Region to indicate that they are implemented: the auxiliary interface:

#Region "iClient"

Private sub taskstarted (BYVAL Controller AS Controller)

Implements iClient.Start

Mactive = TRUE

Label1.text = "start"

Label2.text = "0%"

ProgressBar1.Value = 0

ActivityBar1.Start ()

End Sub

Private sub taskstatus (Byval Text As String) _

Implements iClient.disPlay

Label1.text = Text

Label2.text = cstr (mcontroller.percent) & "%"

ProgressBar1.Value = MController.Percent

End Sub

Private sub taskfailed (Byval e as exception)

Implements iClient.Failed

Activitybar1.stop ()

Label1.text = E.MESSAGE

Msgbox (e.tostring)

Mactive = false

End Sub

Private sub taskcompleted (byval canellled as boolean)

Implements Iclient.comPleted

Label1.text = "completed"

Label2.text = cstr (mcontroller.percent) & "%"

ProgressBar1.Value = MController.Percent

Activitybar1.stop ()

Mactive = false

End Sub

#End region

Note that all content in this section is not related to the thread, each of which can respond to the state of we know the state of the background operation. After each response, we will update the status of the process to indicate the status of the process and complete the percentage (displayed in the form of text), and start and stop the ActivityBar control.

The MACTIVE logo is very important. If the user turns off the form while the auxiliary thread is active, the application may hang or become unstable. To prevent this, we can interrupt the form's closing event and cancel the try (if the background process is active).

Private sub flow1_closing (byval sender as object, _

ByVal e as system.componentmodel.canceleventargs)

Handles mybase.closing

E.cancel = MACTIVE

End Sub

We can also choose to initialize the cancel operation in this case, but depending on the specific application requirements.

The rest of the code is to implement the click Click event of the button.

Private sub btnstart_click (Byval Sender as system.object, _

Byval e as system.eventargs) Handles btnStart.click

MController.Start (New Worker (2000000, 100)) End Sub

Private sub btnstop_click (Byval Sender as system.object, _

Byval e as system.eventargs) Handles btnStop.click

Label1.text = "cancelling ..."

MController.cancel ()

End Sub

The Start button only calls the Start method of the Controller object and passes the instance of the Worker object to it.

You may need to adjust the value used to initialize the Worker object to get the desired result on your computer. These specific values ​​provide a good example on the dual processor P3 / 450 computer. Obviously, this is only used for testing purposes. The real Worker object will achieve more meaningful and run longer.

The Cancel button will call the CANCEL method of the Controller object, and will also update the display to indicate that the request is canceled. Keep in mind that this is just a cancel "request", you may need to wait for some time before the auxiliary thread really stops running. It is best to provide users with instant feedback, at least let the user know that the system has noticed the user's click button operation.

Now we can run the app. When you click the Start button, Worker should start running, and the displayed content will be updated at runtime. You can move the form to any location on the screen, or interact with it because the UI thread is still in the idle state, you can always interact with you.

At the same time, the auxiliary thread performs a lot of complex work in the background and sends the status update information to the UI thread.

summary

Multithreading is a powerful tool that uses this tool while we need to perform tasks for a long time. We can use it to run the auxiliary code without binding the user interface. But also note that multi-threaded operation is very complicated, it is not easy to operate correctly, and it is more difficult to debug.

Although it is not necessarily possible, we should also try to provide a set of independent data that it can operate for each auxiliary thread. To achieve this purpose, the easiest way is to create an object for each thread, and the object contains the thread can operate, and the code required to complete the work.

By implementing a structured architecture, making it acts as a medium between the auxiliary thread and the UI thread, we can greatly simplify the process of writing multi-thread code and UI to control it. This article describes such an architecture that you can use or adjust as needed to meet specific applications.

转载请注明原文地址:https://www.9cbs.com/read-39861.html

New Post(0)