Imagine the following situation:
In a programme, time-intensive computations are executed as a subtask in a procedure P. This means that the interpreter is bound while P is being executed. During the time P is being executed, it does not enter the event loop, which can be regarded as the interpreter's rest mode. There it waits for external events, such as a click on a button. This means - while P is being executed - that the GUI of the process is frozen!
In order to still be able to react promptly to an event, there are two possibilities:
In Gambas, tasks are also used, for example in the gb.form component, to generate a file preview. The trick is this: while the file preview is being generated in a task, the main programme can still interact with the user. It is not in a time-consuming routine and the event loop in the main program runs normally.
A task represents a copy of the parent process (fork → http://de.wikipedia.org/wiki/Fork_%28Unix%29) and runs as an independent process with its own process number completely independently of the calling parent process. Passed variables, for example, can therefore be changed independently in the task without the parent (parent) process noticing anything. Of course, the same applies in the other direction.
In this chapter, in addition to the properties, methods and events of the class Task (gb), you will be introduced to projects that show their use.
The Task class has three properties:
|Handle||Integer||Returns the process number (PID) of the background process (task).|
|Running||Boolean||Returns True if the specified background process is running.|
|Value||Variant||Returns the function value of the Main() function of the background process. If an error occurred in the background process, an error is triggered which can be read and evaluated via Task.Value.|
Table 18.104.22.168.1 : Properties of the class Task
The Task class has only two methods:
In the Task class, these three events are declared:
|Read(Data As String)||The event is triggered when the task outputs data (data type String) to its standard output. Normally, the data is output line by line; unless the task uses the FLUSH statement without an argument. The task standard output can also be used to send task status information to the parent process.|
|Error(Data As String)||The event is triggered when the task outputs data on its error output.|
|Kill()||The event is triggered when the active task has been terminated as a background process.|
Table 22.214.171.124.1 : Events of the Class Task
The event Error(Data AS String) is only triggered if data was written to the standard error output in the background process, for example with the ERROR command!
Contrary to what the event Task_Kill() suggests, the event is not triggered when the task object is destroyed, but when the task process is terminated. Terminated here means:
It is important to emphasise the difference between Task as a background process - in which the Task code is executed - and Task object, which exists in the parent Gambas process. The Task object allows you to communicate with the Task as long as the Task exists. The Task object, however, 'survives' the end of the background process so that you can still read the function value of the Main() function (data type Variant) via the Task.Value property or other properties after it ends.
To have a task processed in the background in a task you need to:
The question of whether a task is necessarily started as a background process when a task object is created can be answered like this:
You can change the priority of a background process via the property Application.Priority (data type Integer) → chapter 20.11 Class Application (gb). The default priority has the value 0.
To increase the priority of a background process (values -1 to -20), root rights are required, which you do not need to decrease the priority (values 1 to 19).
Data transfer between a task as a background process and the parent (parent) process is only uni-directional - i.e. in one direction - in the Task (gb) class:
(A) Programme → Task
In this direction, you can give the task one-time arguments as start values. To do this, you must define public variables in the task class - here MyTask.class - in order to assign suitable (start) values to them in the programme immediately after creating the task.
Private hTask As MyTask Public Sub btnTaskStart_Click() If hTask = NULL then hTask = New MyTask As "MyTask" ' = Task-Klassen-Name hTask.iWaitTime = 3 ' Wertzuweisung für globale Variable in der Klasse MyTask ... End
You can see another approach to passing arguments here:
Public Sub Main() Dim iRow, iCol As Integer Dim hDeterminante As TaskMinors ... For iCol = 0 To $hMatrix.Width - 1 hDeterminante = New TaskMinors($hMatrix, iRow) As "TaskMinors" hDeterminante.Tag = iRow Inc $iTasks '-- Den (internen) Zähler für die gestarteten Tasks erhöhen Next ... End
(B) Task → Programme
The type of data transfer is determined by whether a (function) value is only returned once from the task or whether data is permanently transferred from the task to the main process (clocked, random). If you need data from the running task, you must read this data sent to the standard outputs in the event Task_Read(Data As String) or in the event Task_Error(Data As String) via the PRINT or ERROR command in the Main() method. Note: In the second case, only data of the data type String is allowed.
In the projects presented you will find the different implementations of inter-process communication (IPC).
For a permanent data transmission Task → Programme of data with native data type, Tobias Boege has developed serialisation and deserialisation functions, which are presented in a special project.
The task to be worked on in project 1 does not sound spectacular: With the start of the programme, an analogue clock is to be displayed and parallel to this, the day of the week is calculated and displayed from the current date via a task. Since you need the function value of the Main() function from the task - which is its only purpose in life - once, you can read out the property Task.Value after the background process has ended.
The source code for the Task class DayTask.class is given in full, while only relevant sections of the class FMain.class are presented:
' Gambas class file Inherits Task Public Function Main() As Variant Dim aTagesListe As String aTagesListe = Split("Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag", ",") Return aTagesListe[WeekDay(Now())] '-- Return of the day of the week End
In this class, the day of the week is calculated from the current date and returned as a function value from the Main method.
In the programme, a task is created in the procedure TaskRun():
' Gambas class file Private $hTask As DayTask Public Sub Form_Open() ... '-- Creating a Task: Task Object and Task Process TaskRun() End Private Sub TaskRun() '-- Create a new task object - Object name = Object event name: DayTask If $hTask = Null Then $hTask = New DayTask As "DayTask" Repeat Wait 0.001 Until $hTask <> Null End Public Sub DayTask_Kill() Dim DayOfWeek As String Dim sErrorMessage As String '-- Save process return value. Alternative: Last.Value Try DayOfWeek = $hTask.Value If Not Error Then lblDayOfWeek.Text = DayOfWeek Else sErrorMessage = "Error!" & gb.NewLine sErrorMessage &= Error.Where & gb.NewLine sErrorMessage &= Error.Text Message.Error(sErrorMessage) lblDayOfWeek.Text = "Task error" Endif End ...
The analogue clock is displayed and below it the weekday calculated once in the task for the current date - if no error occurred in the task:
Figure 126.96.36.199.1: Display of time and weekday
You can create an error quite easily by using a semicolon instead of the correct separator comma, for example:
aTagesListe = Split("Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag", ";")
The display changes in the lower part because in the procedure 'Public Sub DayTask_Kill()' the error is detected and documented:
Figure 188.8.131.52.2: Display of time and error message
You can see very clearly again in the last case that with the creation of the task after the fork into two independent processes, the clock runs completely independent of the task and its return value.