Worker Thread API
Worker threads are a means to execute different tasks in multiple parallel contexts of execution in a concurrent manner, which can take advantage of multiprocessor and multithreaded environments as well as to keep UI Thread in Application responsive by delegating or offloading work which need not be handled in UI Main thread, to a different secondary thread.
Worker thread is a continuous parallel thread that runs and accepts messages until the time it is explicitly closed or terminated. Messages to a worker thread can be sent from the parent thread or its child worker threads. Through out this document, parent thread is referred as thread where a worker thread is spawned.
Worker can have logic that gets executed parallel for each of the messages that it receives. If worker thread is busy handling messages, incoming messages that it receives will be queued for processing.
Sharing data between parent thread and worker threads is done through message passing and by default variables, functions, or state is not shared.
The current specification is inspired from and based on HTML5 Web Worker threads standard, that use Message Passing model or mechanism for communication between threads.
NOTE: To use AWS in workerthreads, you must follow these steps.
1. Copy all AWS related files from <workspace>/<appname>/cloudsdks folder into the <workspace>/<appname>/workerthreads folder of the project. This is because while importing the AWS files using Visualizer, the Kony HttpRequest and DOMParser related code are added to the AWS files. These modified files are then saved in the cloudsdks folder by Visualizer.
2. After the files have been copied, use the require(<aws files>)
code in the workerthread.js
file to import all the AWS files into worker threads context.
AWS objects must be created in the worker thread. Any object created in the regular thread will not work in the worker thread.
The Worker Thread API contains the following API Elements:
Function | Description |
---|---|
kony.worker.hasWorkerThreadSupport | Determines whether the current platform environment has worker thread support. |
kony.worker.WorkerThread | Creates a WorkerThread object and returns a handle to it. The worker object represents a worker thread. |
Method | Description |
---|---|
addEventListener | Event Handlers can be registered using addEventListener() method on the worker Objects and once registered messages and errors from a worker thread can be received in parent thread. |
close | Worker thread can be terminated from inner scope of the worker by invoking close(). The worker thread is killed immediately without an opportunity to complete its operations or clean up. |
postMessage | postMessage() sends a JSON object or String message to the Parent/worker's scope by invoking respective registered "message" event handlers. |
removeEventListener | removeEventListener() is used to remove the previously registered message or error event listener that was registered using addEventListener(). |
terminate | When called from parent scope immediately terminates the worker. This does not offer the worker an opportunity to finish its operations. It is simply stopped at once. |
Scope
- Quantum Visualizer platform version >= 5.6.2.
- Support for JavaScript.
- Supported mobile Platforms:
- iOS
- Android
DesktopWeb
IE | Firefox | Chrome | Safari |
---|---|---|---|
10.0 | 4.0 | 20 | 5.0 |
Introduction to Constructor - WorkerThread()
- The WorkerThread() constructor creates and returns the handle to the newly created worker thread. The new worker thread can be used by the Parent thread for any further communication with the worker thread.
- To create a worker thread, it requires a JavaScript file name or a functional module name. The WorkerThread() constructor is invoked with the JavaScript file or a functional module name as its only argument and a worker thread instance is then created and returned.
- Worker threads may in turn initiate new worker threads.
- If a Javascript file name with ".js" extension is passed as WorkerThread() constructor argument, it looks up only in the
workerthreads
directory inmodules/js
path and loads if the file is found. This holds good for functional modules based projects as well as non-functional modules based projects. - If a function module name is provided as an argument for WorkerThread() constructor, in case of functional modules based project then the module will be loaded if found in the modules listing.
Worker Thread Scenarios
The scenarios of using WorkerThread() constructor are as follows:
- The WorkerThread() constructor creates a new worker thread and returns a handle to the new worker thread, which can be used by the parent thread for any further communication with the worker thread.
Creating a worker thread requires a JavaScript file name or a functional module name. The WorkerThread() constructor is invoked with the JavaScript file or a functional module name as its only argument and a worker thread instance is then created and returned:
var worker = new kony.worker.WorkerThread('helper.js');
var worker = new kony.worker.WorkerThread('functionModuleName');
- A message event handler can be registered with the worker by parent thread to receive messages from the worker thread.
worker.addEventListener("message", function (event) { ... });
- To send data from parent to a worker, postMessage() method can be used from parent.
worker.postMessage({ operation: 'find-edges', input: 'buffer', threshold: 0.6 } );
- To send messages back from worker thread to parent thread scope, postMessage() can be used.
postMessage({'msg':'Data'});
- To receive a messages inside the worker thread from parent thread, the message event handler can be registered using addEventListener() inside worker thread.
self.addEventListener( "message", function (event) { ... });
Worker Thread Life Cycle
The following steps provide the work flow to use worker thread:
Call to Worker constructor will create a new Worker instance and a new parallel execution environment context is created, and immediately starts execution in the new parallel thread of control in an asynchronous manner. In this new thread, first the Worker will try to load the ‘workerjs’ script.
As a result of the asynchronous parallel nature of execution in worker thread context, invocation of Worker constructor call in Parent thread will return a new Worker instance handle and Parent proceeds with execution of next instructions.
Every Worker thread will have its own event loop which takes care of the execution of all the received message tasks which are queued for this worker in that order until ‘self.close()’ in worker scope or ‘worker.terminate()’ in parent worker scope are invoked.
From the moment of successful creation of worker thread and until ‘self.close()’ in worker scope or ‘worker.terminate()’ in parent worker scope are invoked, the worker thread will be alive and can receive and process messages which are sent to this worker form its parent or from its child workers if created, as well as it can send messages using postMessage() to its parent thread and its child worker threads if created.
message event handler
"message" event handler receives an "event" object which contains the JSON or string message that is passed to postMessage() during invocation and the same message can be accessed from its "event.data" field. The data passed to postMessage() should be a String or JSON object.
Adhering to the JSON standard, the JSON object passed to postMessage() API should be serializable JSON without opaque object handles or function object handles etc. The data which is passed between the parent thread and worker thread using postMessage() API are copied, not shared, so the end result is that a duplicate is created on each end.
Multiple "message" event handlers can also be registered in Parent scope and in workers inner scope and all the registered event handlers will be invoked in the registered order whenever a postMessage() is called.
Syntax
function(event) { });
Input Parameters
String / JSON Object
- "message" event handler receives an "event" object which contains the JSON or string message that is passed to postMessage() during invocation and the same message can be accessed from its "event.data" field.
- The data passed to postMessage() should be a String or JSON object.
- Adhering to the JSON standard, the JSON object passed to postMessage() API should be serializable JSON without opaque object handles or function object handles etc.
- The data which is passed between the parent thread and worker thread using postMessage() API are copied, not shared, so the end result is that a duplicate is created on each end.
- Multiple "message" event handlers can also be registered in Parent scope and in workers inner scope and all the registered event handlers will be invoked in the registered order whenever a postMessage() is called.
Example
var evtMessageHandler_1 = function(event) { //In case of JSONkony.print ("Received message :" + event.data["msg"]);" //In case of string kony.print ("Received message :" + event.data); };
Platform Availability
- iOS
- Android
- Desktop Web
For more information, see Scope.
error event handler
"error" event handler receives an "event" object which has the has the following three fields: message, filename, lineno. Registered ‘error’ event handler is invoked whenever an unhandled exception arises in worker’s scope. "error" event handler can be registered in parent thread scope on worker object and as well as in worker thread’s inner scope, where both event handlers will be invoked if present whenever an unhandled exception occurs in workers inner scope.
Multiple "error" event handlers can also be registered in Parent scope and in workers inner scope and all registered event handlers will be invoked in the registered order whenever an unhandled exception arises in worker’s scope.
Syntax
function(event) { });
Input Parameters
error event object (message, filename, lineno)
Example
function(event) { kony.print ('ERROR: Line '+ event.lineno + ' in ' + event.filename + ': ' + event.message); }
Platform Availability
- iOS
- Android
- Desktop Web.
For more information, see Scope.
Importing scripts
Worker threads can use importScripts() function to import external scripts their scope by providing the JS file name to import. This method takes one or more JavaScript file names to import.
This API is only available in worker thread scope and not in main parent thread scope.
In case of Functional modules based project " kony.modules.loadFunctionalModule()" API can be used to import a functional module into workers scope. Refer Functional Modules specification document for usage help on loadFunctionalModule() API.
importScripts() if invoked with .js file, looks up only in the "workerthreads" directory under "modules/js/" in Quantum Visualizer IDE Project structure to import scripts into workers scope. This holds good for both functional modules based projects and non-functional modules based projects.
In case of loading multiple files using importScripts(), if an error occurs while loading one of the script, then the remaining scripts are not loaded into context scope.
For more information on Functional Module APIs, refer Functional Modules APIs.
Syntax
importScripts(".js_file_name");
or
importScripts("functional_module_name");
Input Parameters
JSFileNames [Object]
or
Functional_Module_Name [Object]
- One or more comma separated list of JavaScript file names.
Example
importScripts("Utility.js"); // loads Util.js importScripts("Utility1.js", "Utility2.js", "Utility3.js");
Return Values
None
Exceptions
NOTE: If no argument is given, no exception is raised and it does nothing.
When an error is encountered, the KonyError JS object is thrown with the following information:
Error Code | Name | Message | Reason |
---|---|---|---|
3002 | WorkerThreadError | importScripts: InvalidParameter. Invalid script name | This exception occurs when the argument passed is not a string. |
3002 | WorkerThreadError | importScripts: InvalidParameter. Unable to import script. <scriptname> | This exception occurs when it is unable to find and load the JS script. |
- In worker scope, if these exceptions are not handled and if an error event handler is registered in worker’s inner scope or/and in parent scope for this worker object, then it is invoked with an error event object and its message attribute is set as follows:
Exception 1 - message: "importScripts: InvalidParameter. Invalid script name"
Exception 2 - message: "importScripts: InvalidParameter. Invalid script name"
Differences in behavior of importScripts() and kony.modules.loadFunctionalModule() API with respect to Functional Modules:
Without Functional Modules | With Functional Modules |
---|---|
From inside Worker context if importScripts() is used to import external JS scripts the search criteria would be : only "workerthreads" directory. | From inside Worker context if importScripts() is used to import external JS scripts the search criteria would be : only "workerthreads" directory. |
kony.modules.loadFunctionalModule() function cannot be used in workers scope to load any FunctionalModule. | kony.modules.loadFunctionalModule() function can be used in workers scope to load any JavaScript script which is part of some Functional Module. |
Platform Availability
Available for iOS, Android, and Desktop Web. For more information, see Scope.
Using Worker Threads Feature
The following topics helps you to use the worker thread feature:
- Communicating and Data Processing Between Threads
- Nesting of Threads and Performing Parallel Tasks
- Scope Rules and Supported APIs
- FFI and Custom Widgets
- Guidelines and Limitations
- Debugger Support
Communicating and Data Processing Between Threads
Main.js
//create new worker var worker = new kony.worker.WorkerThread('1_worker.js'); //invoked when worker calls postmessage() from its inner scope worker.addEventListener("message", function (event) { kony.print('Parent Scope : onmessage : event.data : ' + event.data["message"]); }); kony.print('Parent Scope : Invoking worker.postmessage()'); //will invoke worker's inner scope onmessage() worker.postMessage({ 'message': 'Hello World From Parent' });
1_worker.js
//workers inner scope //invoked when Parent calls worker.postmessage() self.addEventListener("message", function (event) { kony.print('Worker Scope : onmessage : event.data : ' + event.data["message"]); //call func do_something_in_worker(); }); function do_something_in_worker() { kony.print('Worker Scope : invoking postMessage()'); //will invoke Parent worker.onmessage() postMessage({ 'message': "Hello World From Worker " }); };
Expected Output
"Parent Scope: Invoking worker.postmessage()" "Worker Scope: onmessage : event.data : " "Hello World From Parent" "Worker Scope: invoking postMessage()" "Parent Scope: onmessage : event.data : " "Hello World From Worker "
Explanation
- In Parent Scope: Creates new worker using new kony.worker.WorkerThread ()
- In Parent Scope: Call to worker.postMessage() invokes message event handler registered using addEventListener() in worker threads inner scope.
- In Worker Scope: Call to postMessage() invokes message event handler registered using addEventListener() in the parent thread scope.
Nesting of Threads and Performing Parallel Tasks
Main.js
try { kony.print("Parent Scope: Init test_case_parent_thread()"); kony.print("Parent Scope: In try block"); //create new kony.worker.WorkerThread var worker = new kony.worker.WorkerThread('WorkerThread.js'); //invoked when worker calls postmessage() from its inner scope worker.addEventListener("message", function (event) { kony.print('Parent Scope : onmessage : event.data : ' + event.data); }); worker.postMessage("Hello from Parent"); } catch (err) { kony.print("Parent Scope: In Catch block"); } //invoke a function invoke_timer_task(); // function invoke_timer_task() { kony.print("Parent Scope :- kony.timer.schedule - "); var timerId = "mytimer12111"; var i = 0; function timerFunc() { i++; kony.print("Parent Scope :- kony.timer.schedule - In timerFunc() : " + i); if (i > 20) { kony.print("Parent Scope :- kony.timer.schedule - Stopping timer : "); kony.timer.cancel(timerId); } }; // kony.timer.schedule(timerId, timerFunc, 1, true); kony.print("Parent Scope :- kony.timer.schedule - Done"); }; kony.print("Parent Scope: Exit test_case_parent_thread()");
WorkerThread.js
//worker //workers inner scope kony.print("Worker Scope: Init"); var worker = new kony.worker.WorkerThread('WorkerThread2.js'); //invoked when Parent calls worker.postmessage() this.addEventListener("message", function(event) { kony.print('Worker Scope : onmessage : event.data : ' + event.data); }); self.postMessage("Hello from Worker"); // invoke_timer_task(); // function invoke_timer_task() { kony.print("Worker Scope :- kony.timer.schedule - "); var timerId = "mytimer121"; var i = 0; function timerFunc() { i++; kony.print("Worker Scope :- kony.timer.schedule - In timerFunc() : " + i); if(i > 20) { kony.print("Worker Scope :- kony.timer.schedule - Stopping timer : "); kony.timer.cancel(timerId); } }; // kony.timer.schedule(timerId,timerFunc, 1, true); kony.print("Worker Scope :- kony.timer.schedule - Done"); }; kony.print("Worker Scope: Loading done");
WorkerThread2.js
//Grand child worker2 – nested worker //workers inner scope kony.print("Grand child: Worker2 Scope: Init"); //invoked when Parent calls worker.postmessage() this.addEventListener("message", function(event) { kony.print('Grand child: Worker2 Scope : onmessage : event.data : ' + event.data); }); self.postMessage("Hello from Worker2"); // invoke_timer_task(); // function invoke_timer_task () { kony.print("Grand child: Worker2 Scope :- kony.timer.schedule - "); var timerId = "mytimer1211"; var i = 0; function timerFunc() { i++; kony.print("Worker2 Scope :- kony.timer.schedule - In timerFunc() : " + i + " : Grand child "); if(i > 20) { kony.print("Grand child: Worker2 Scope :- kony.timer.schedule - Stopping timer : "); kony.timer.cancel(timerId); } }; // kony.timer.schedule(timerId,timerFunc, 1, true); kony.print("Grand child: Worker2 Scope :- kony.timer.schedule - Done"); }; kony.print("Grand child: Worker2 Scope : Loading done");
Scope Rules and Supported APIs
Global resources in the App context will not be available in the worker thread context as it can lead to Race conditions since no locking mechanisms are provided.
Every worker has its own context of execution, which is not shared between the parent and its worker. As a result the global variables in parent scope are not available in worker scope and vice versa.
Not Supported APIs |
| |||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Supported APIs and Platform |
|
FFI and Custom Widgets
With and without Functional Module in Worker context:
iOS | Android | |
---|---|---|
FFI | Platform will load modules by default | |
Custom Widgets | No need to load Custom Widgets in worker scope. |
Guidelines and Limitations
The following guidelines are recommended before using worker thread:
- The objects "this" and "self" are available in worker thread inner scope that is referred as worker thread itself.
- Event propagation cannot be stopped by using event.stopPropagation() as in HTML specification. Where event.stopPropagation() stops the DOM event to be propagated further by breaking the event chain.
- Data passed between the main thread and workers are copied, but not shared. Objects are serialized as they are handed to the worker, and subsequently, de-serialized on the other end. The main thread and worker do not share the same instance. So the end result is that a duplicate is created on each end. HTML5 worker threads support transferable objects that allow transferring the objects from one thread to other without making a copy.
As explicit thread synchronization mechanisms like locking or mutexes are not available in JS environment, you must take required care in scenarios where multiple threads concurrently or simultaneously are trying to access and write/insert data into local database or local datastore using WebSQL or Local datastore APIs, as these are shared resources across the Application context.
- The outcome of these simultaneous or concurrent accesses to database/datastore might push the database/datastore to inconsistent state and is dependent on the individual platforms WebSQL or DataStore implementations. It is always suggested to avoid such scenarios of multiple threads accessing the database/datastore simultaneously/concurrently.
Limitations in DesktopWeb:
- Nested Workers support will be available only if they are supported by the underlying browser platforms.
- Error event messages in error event handler sometimes might be appended with extra prefix or suffix string messages from underlying browsers (like Uncaught, Uncaught error etc).
- Even though error event handler is invoked in case of unhandled exceptions, the same event messages might also be logged on browser console.
- Line number, file name populated in error event object passed to error event handler might be different from user defined files.
NOTE: For Desktop Web, nested workers are not supported in Google Chrome.
terminate API
As the worker threading model is mapped to underlying native threading models in native platforms, there can be some deviations from what specification says, this is due to technical limitations in the underlying platforms which include:
- If terminate() is invoked in Parent thread on worker, and if worker is currently executing a large task, it might not immediately terminate, it will continue to execute the task to completion and terminate.
- If terminate() is invoked in Parent thread on worker, and if there are pending tasks waiting in the message queues for this worker to perform, some platforms might discard all the pending tasks without accepting them for execution and terminate, and on some platform all the pending tasks are executed to completion and then the worker terminates.
- It is to be noted that to achieve cross platform consistency, it is always advised that terminate() be invoked on the worker once all the tasks in message queues are completed.
close API
- As the worker threading model is mapped to underlying native threading models in native platforms, there can be some deviations from what specification says, this is due to technical limitations in the underlying platforms which include:
- When close() is invoked in worker scope, and if worker is currently executing a large task, it might not immediately terminate, it will continue to execute the task to completion and then terminate.
- When close () is invoked in worker scope, and if there are pending tasks waiting in the message queues for this worker to perform, some platforms might discard all the pending tasks without accepting them for execution and terminate, and on some platform all the pending tasks are executed to completion and then the worker terminates.
- Hence it is to be noted that to achieve cross platform consistency, it is always advised that close() be invoked in worker scope once all the tasks in message queues are completed.
Debugger Support
Debugger support for worker threads is not available in 5.6.2 release.
Worker Life Cycle Notes
Following are some worker life cycle notes:
- Call to Worker constructor will create a new Worker instance and a new parallel execution environment context is created, and immediately starts execution in the new parallel thread of control in an asynchronous manner. In this new thread, first the Worker will try to load the ‘workerjs’ script.
- As a result of the asynchronous parallel nature of execution in worker thread context, invocation of Worker constructor call in Parent thread will return a new Worker instance handle and Parent proceeds with execution of next instructions.
- Every Worker thread will have its own event loop which takes care of the execution of all the received message tasks which are queued for this worker in that order until ‘self.close()’ in worker scope or ‘worker.terminate()’ in parent worker scope are invoked.
- From the moment of successful creation of worker thread and until ‘self.close()’ in worker scope or ‘worker.terminate()’ in parent worker scope are invoked, the worker thread will be alive and can receive and process messages which are sent to this worker form its parent or from its child workers if created, as well as it can send messages using postMessage() to its parent thread and its child worker threads if created.
- In worker thread scope when there is no "message" event handler registered which indicates that no tasks can be posted or queued by parent for this worker, and if no callbacks are registered in this worker scope for network, timer APIs etc, then after postMessage() call in Workers inner scope if any, worker can terminate itself without worker.terminate() being called in Parent scope or self.close() called in inner scope.
- In case of orphaned threads if no tasks are queued in message handler and if the worker is not waiting for any callbacks to be returned from network, timer APIs etc then the orphaned thread can be terminated.
- If Parent stops either due to unhandled exception raised in it scope, Worker threads still continues to exist and execute.