BackgroundWorkers in Smart Office Scripts – Part 1

In this article I illustrate how to set input parameters and how to receive output values with a BackgroundWorker in Personalized Scripts for Lawson Smart Office. This article is a first in a set of articles about BackgroundWorkers to ensure code quality and usability.

As a reminder, time-consuming operations like M3 API calls, Web Service calls, or HTTP Requests, should be placed in a background thread to not freeze the user interface and to not result in bad usability. Refer to Karin’s post about Calling M3 APIs in JScript on a Background Thread.

Knowing how to set input parameters and how to receive output values with a BackgroundWorker will exempt us from using global variables; global variables in a multithreaded program are not thread safe because of possible race conditions that are hard to troubleshoot.

Counter-example

Suppose we have the following simple script that calls a time-consuming operation not in a BackgroundWorker; the time-consuming operation is in the UI thread and will freeze the user interface:

import System.Windows;

package MForms.JScript {
    class Test {
        public function Init(element: Object, args: Object, controller: Object, debug: Object) {
            var inputParameters = ...;
            var outputValues = doSomethingTimeConsuming(inputParameters);
        }
    }
}

BackgroundWorker

A BackgroundWorker executes code in a background thread without blocking the UI thread. There are many more lines of code involved: a BackgroundWorker is started with RunWorkerAsync(), the event handler OnDoWork runs in the background thread with the time-consuming operation, the event handler  OnDunWorkerCompleted resumes in the UI thread, and we cleanup the worker after use. That advantage of BackgroundWorkers is that the UI doesn’t freeze, but the disadvantage is that the code must be carefully crafted and it’s not obvious.

Here’s a simple script that calls a time-consuming operation in a BackgroundWorker; as the time-consuming operation is in the background thread it will not freeze the user interface:

import System.ComponentModel;

package MForms.JScript {
    class Test {
        /* UI thread */
        public function Init(element: Object, args: Object, controller: Object, debug: Object) {
            var worker = new BackgroundWorker();
            worker.add_DoWork(OnDoWork);
            worker.add_RunWorkerCompleted(OnRunWorkerCompleted);
            worker.RunWorkerAsync();
        }
        /* background thread */
        function OnDoWork(sender: Object, e: DoWorkEventArgs) {
            doSomethingTimeConsuming();
        }
        /* UI thread */
        function OnRunWorkerCompleted(sender: Object, e: RunWorkerCompletedEventArgs) {
            var worker: BackgroundWorker = sender;
            worker.remove_DoWork(OnDoWork);
            worker.remove_RunWorkerCompleted(OnRunWorkerCompleted);
        }
        /* something time-consuming */
        function doSomethingTimeConsuming() {
            System.Threading.Thread.Sleep(1000);
        }
    }
}

How to set input parameters

To pass input parameters to a BackgroundWorker we don’t have to use global variables; we can set the input values as the argument of RunWorkerAsync and get e.Argument in OnDoWork:

public function Init(element: Object, args: Object, controller: Object, debug: Object) {
    ...
    var inputParameters = ...;
    worker.RunWorkerAsync(inputParameters);
}
function OnDoWork(sender: Object, e: DoWorkEventArgs) {
    var inputParameters = e.Argument;
    ...
}

How to receive output values

To get output values from a BackgroundWorker we don’t have to use global variables either; we can set e.Result in OnDoWork and get e.Result in OnRunWorkerCompleted:

function OnDoWork(sender: Object, e: DoWorkEventArgs) {
    ...
    var outputValues = ...;
    e.Result = outputValues;
}
function OnRunWorkerCompleted(sender: Object, e: RunWorkerCompletedEventArgs) {
    var outputValues = e.Result;
    ...
}

Final source code

Here is the final source code illustrating how to create a BackgroundWorker, do the time-consuming operation in the background thread, set input parameters and get output values (I use object literals), cleanup the worker, and not use global variables.

import System.ComponentModel;

package MForms.JScript {

    class Test {

        /* UI thread */
        public function Init(element: Object, args: Object, controller: Object, debug: Object) {
            // set input parameters for the background thread
            var inputParameters = {
                'CONO': 100,
                'CUNO': 'ACME'
            };
            // create and start worker
            var worker = new BackgroundWorker();
            worker.add_DoWork(OnDoWork);
            worker.add_RunWorkerCompleted(OnRunWorkerCompleted);
            worker.RunWorkerAsync(inputParameters);
        }

        /* background thread */
        function OnDoWork(sender: Object, e: DoWorkEventArgs) {
            // get input parameters from the UI thread
            var inputParameters = e.Argument;
            var CONO = inputParameters.CONO;
            var CUNO = inputParameters.CUNO;
            // do something time-consuming
            doSomethingTimeConsuming();
            // return output values to the UI thread
            var outputValues = {
                'CUNM': 'A Company Manufacturing Everything',
                'CUA1': '123 Main St.',
                'CUA2': 'SAN FRANCISCO, CA'
            };
            e.Result = outputValues;
        }

        /* UI thread */
        function OnRunWorkerCompleted(sender: Object, e: RunWorkerCompletedEventArgs) {
            // get output values from the background thread
            var outputValues = e.Result;
            var CUNM = outputValues.CUNM;
            var CUA1 = outputValues.CUA1;
            var CUA2 = outputValues.CUA2;
            // cleanup worker
            var worker: BackgroundWorker = sender;
            worker.remove_DoWork(OnDoWork);
            worker.remove_RunWorkerCompleted(OnRunWorkerCompleted);
        }

        function doSomethingTimeConsuming() {
            System.Threading.Thread.Sleep(1000);
        }

   }
}

Conclusion

In this article I illustrated how to set input parameters and how to receive output values with a BackgroundWorker in Personalized Scripts for Lawson Smart Office. Knowing these techniques is important for code quality, but the code may be delicate to craft.

Next

In my next article I will illustrate:

  • How to disable/enable the user interface
  • How to indicate activity
  • How to show progress
  • How to handle worker cancellation
  • How to handle worker errors
  • How to handle exceptions

Related articles

All articles in this series:


UPDATE: 2012-08-06: Consolidated motive and examples, and added conclusion.

Published by

thibaudatwork

M3 Technical Consultant

3 thoughts on “BackgroundWorkers in Smart Office Scripts – Part 1”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s