BackgroundWorkers in Smart Office Scripts – Part 3

In this third part of my series on BackgroundWorkers in Lawson Smart Office Scripts, I illustrate how to handle worker cancellation. This article is a continuation of my previous articles, Part 1 and Part 2.

When a worker is processing a time-consuming operation in a background thread, the user interface remains available and the user could potentially take any of the available actions, for example close the window (F3), refresh the M3 program (F5), click Previous (F12), click Next (ENTER), select an Option (CTRL+1-5), select a Related Option (CTRL+6-99), and go to Settings (F13). In those cases, I want to cancel my worker to stop the processing in a controlled way and avoid abrupt termination.

Fortunately, the life-cycle of Smart Office scripts includes three events I can listen to: Requesting, Requested, and RequestCompleted. I’ll intercept the user actions in those events and I’ll cancel the worker there. For more information on those events, refer to the Lawson Smart Office Developer’s Guide.

To cancel a worker, I use WorkerSupportsCancellation, CancellationPending, e.Cancel, IsBusy, WorkerSupportsCancellation, CancelAsync, and Cancelled.

Steps

First, I listen for the user actions:

controller.add_Requesting(OnRequesting);
controller.add_Requested(OnRequested);

Then, I ensure the worker supports cancellation:

worker.WorkerSupportsCancellation = true;

In the background thread, I do the time-consuming operation as long as there’s no cancellation pending, otherwise I cancel the worker and stop the time-consuming operation:

function OnDoWork(sender: Object, e: DoWorkEventArgs) {
    var worker: BackgroundWorker = sender;
    while (someCondition) {
        doSomethingTimeConsuming();
        if (worker.CancellationPending) {
            e.Cancel = true;
            return;
        }
    }
    ...
}

In the UI thread, if the user takes an action I’ll intercept it in OnRequesting and I will instruct the worker to cancel processing:

// F3-Close, F5-Refresh, F12-Cancel, Option 5-Display, Previous/Next, ENTER, etc.
function OnRequesting(sender: Object, e: CancelRequestEventArgs) {
    if (worker.IsBusy && worker.WorkerSupportsCancellation) {
        worker.CancelAsync();
    }
}

Then, the life-cycle of the script will reach OnRequested where I clean-up my script:

function OnRequested(sender: Object, e: RequestEventArgs) {
    sender.remove_Requesting(OnRequesting);
    sender.remove_Requested(OnRequested);
}

Eventually, the worker will receive the instruction to cancel processing, and in OnRunWorkerCompleted I will do what’s necessary to stop processing in a controlled way and avoid abrupt termination.

function OnRunWorkerCompleted(sender: Object, e: RunWorkerCompletedEventArgs) {
    if (e.Cancelled) {
        // cancel
    } else {
        // continue
    }
    ...
}

Final source code

Here’s the full source code tested in Smart Office 10.0.4.0.38:

import System.ComponentModel;
import Mango.UI;
import MForms;

package MForms.JScript {

    class Test {

        var debug: Object;
        var worker: BackgroundWorker;

        public function Init(element: Object, args: Object, controller: Object, debug: Object) {
            this.debug = debug;
            debug.WriteLine('start');
            controller.add_Requesting(OnRequesting);
            controller.add_Requested(OnRequested);
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;
            worker.add_DoWork(OnDoWork);
            worker.add_ProgressChanged(OnProgressChanged);
            worker.add_RunWorkerCompleted(OnRunWorkerCompleted);
            worker.RunWorkerAsync();
        }

        function OnDoWork(sender: Object, e: DoWorkEventArgs) {
            var worker: BackgroundWorker = sender;
            var n = 10;
            for (var i = 0; i < n; i++) {
               worker.ReportProgress(i/n*100, 'processing');
               doSomethingTimeConsuming();
                if (worker.CancellationPending) {
                    e.Cancel = true;
                    return;
                }
            }
        }

        function OnProgressChanged(sender: Object, e: ProgressChangedEventArgs) {
            debug.WriteLine(e.ProgressPercentage + '% ' + e.UserState);
        }

        // F3-Close, F5-Refresh, F12-Cancel, Option 5-Display, Previous/Next, ENTER, etc.
        function OnRequesting(sender: Object, e: CancelRequestEventArgs) {
            debug.WriteLine('OnRequesting');
            if (worker.IsBusy && worker.WorkerSupportsCancellation) {
                debug.WriteLine('cancelling');
                worker.CancelAsync();
            }
        }

        function OnRequested(sender: Object, e: RequestEventArgs) {
            debug.WriteLine('OnRequested');
            sender.remove_Requesting(OnRequesting);
            sender.remove_Requested(OnRequested);
        }

        function OnRunWorkerCompleted(sender: Object, e: RunWorkerCompletedEventArgs) {
            if (e.Cancelled) {
                // cancel
                debug.WriteLine('cancelled');
            } else {
                // continue
                debug.WriteLine('done');
            }
            // cleanup
            var worker: BackgroundWorker = sender;
            worker.remove_DoWork(OnDoWork);
            worker.remove_RunWorkerCompleted(OnRunWorkerCompleted);
            debug.WriteLine('cleaned-up');
         }

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

    }
}

Result

A normal execution will result in this output:

start
0% processing
10% processing
20% processing
30% processing
40% processing
50% processing
60% processing
70% processing
80% processing
90% processing
done
cleaned-up

A cancelled execution will result in this output, for example if the user presses F5-Refresh:

start
0% processing
10% processing
20% processing
30% processing
OnRequesting
cancelling
OnRequested
cancelled
cleaned-up

Here’s a screenshot of both results:

Conclusion

In this article I illustrated how to intercept actions the user might take in the user interface while a time-consuming operation is in progress in a background thread, and eventually cancel the worker to stop the processing in a controlled way and avoid abrupt termination.

Next

In my next article I will illustrate how to handle worker errors and how to handle exceptions.

Related articles

All articles in this series:

Published by

thibaudatwork

ex- M3 Technical Consultant

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

  1. Hi Thibaud, deployed my php program to call a webservice by getting “Message: Function (“LstByNumber”) is not a valid method for this service”

    It is in the wsdl, but cached one that doesnt have this method?… How do I clear?
    Cheers Murray

    Like

    1. Hi Murray, it’s hard to troubleshoot your case with only little information. I suggest you first test your web service with SoapUI to validate it’s working; that will give you a reference SOAP Request. Then run your PHP code that calls the same web service. Intercept that call with Fiddler or Wireshark; that will give you a second SOAP Request which you say is failing. Compare both SOAP Requests and check for differences. There must be a significant difference that causes the error. Hope it helps.

      Like

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 )

Facebook photo

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

Connecting to %s