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:
- BackgroundWorkers in Smart Office Scripts – Part 1 – How to set input parameters, and how to receive output values
- BackgroundWorkers in Smart Office Scripts – Part 2 – How to disable/enable the user interface, how to indicate activity, and how to show progress
- BackgroundWorkers in Smart Office Scripts – Part 3 – How to handle worker cancellation
- BackgroundWorkers in Smart Office Scripts – Part 4 – How to handle worker errors and how to handle exceptions
..and beyond!!. Thanks dude!
LikeLike
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
LikeLike
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.
LikeLike