Tech News Back Issues Issue: 062305

Providing Global Resources in Multiple Task Applications

By David Swain
Polymath Business Systems

This article continues our exploration of the use of tasks in Omnis Studio. In previous articles, we have discussed what task instances are and how they can be used to manage aspects of our applications. We have also seen how Omnis Studio deals with changes of context as either the user or the execution of our methods switches from one task instance to another. We have learned the difference between the current task and the active task and have seen how to make certain instances within a task local to that task. We have even learned that we can launch a new instance into a task from a method executing inside a different task instance, indicating that there is perhaps more flexibility and power at our disposal than we might have imagined at first glance.

So let's assume that we have decided to use multiple tasks within an application. Now we are faced with the problem of how to provide various application resources that can be accessed from anywhere within the application. In a single task application, this is easy to do - we can create task variables that can be accessed from within any instance contained in the task. But now we must find a way to share such resources across task boundaries.

What Do We Mean By "Global Resources"?

Global resources are items that we may need to access from anywhere within our application. For example, Omnis Studio provides many global resources for us: The date and time variables (#D and #T) are obvious cases. But all of the built-in and external functions and constants, class and component object type definitions and even localized character sets and label strings are available to us throughout an application without our having to instantiate them for each new window instance - or even for each new task instance. They are just "there" for us to use.

Most applications we build will have similar resource needs that are more application specific and that could not have been anticipated by the people who created Omnis Studio. An application with even rudimentary security will need to keep track of certain information about the current user and their current session within the application, for example. Access to SQL data sources may be through a session object that we desire to make global. There may be many custom functions that are required in diverse parts of the application for performing common calculations or other operations. The question is: How do we provide such resources so that they can be shared among the instances of our application, regardless of which task is the current or active one?

Inheritance of Task Superclasses

A partial solution can be found by using a "master task" superclass containing such resources. We could create this superclass to contain all our global methods directly or to contain task variables of object type that hold collections of such methods. We could also include other task variables that we may need to access "globally" as well, such as custom "constants" for certain calculations (such as an interest rate or engineering factor) or "states" that are specific to the current runtime (like the path to a local output directory). Then the task classes for our application could be subclassed from this superclass and all of them will contain the same methods and variables automatically.

But there is a problem with this approach. Yes, all these tasks contain the same variables and methods, inherited from the superclass, so they can be accessed from any location in any instance with the same code. But the variables of each task are completely independent of their counterparts in the other tasks. The subclasses do not share the task variables of the superclass, but simply make copies of them - actually, only copies of their definitions, so assigned values (other than default calculations) aren't even copied. If we chose to house most of our custom functions in object classes for the flexibility and portability they offer, then we have created multiple copies of these methods as well. Variable values contained within these object instances are not shared either, so changes made to such values inside one task instance are not automatically reflected in other task instances. The inheritance technique only gets us part of the way to a solution.

A partial solution to this problem exists. We could create our resources in a single task instance (perhaps the startup task), create a notational reference to this task instance and then pass that reference to our other tasks. This will work for many cases, but it limits our flexibility. There could still be scoping problems with this approach, for example. Still, for Omnis Studio 3 and earlier, this can be used effectively.

But users of Omnis Studio version 4 have another option...

Object Reference Variables at the Task Level

Object instances are wonderful for use as variable and method repositories. In fact, these combinations of variables and methods can be very useful for retaining and managing data (like a security object that can retain current user information and create new user tracking records for significant events we wish to monitor). But passing objects around can be a cumbersome chore. We all soon found that a more flexible technique for working with objects was needed for many cases.

Omnis Studio version 4.0 introduced the object reference data type. While object variables contain an instance of an object class, object reference variables only point to an instance of an object class. This is an important distinction. When an object variable is passed as a parameter, a new object instance is created which is a duplicate of the instance contained in the original object variable whose value was passed. That object then goes on to have a completely independent life of its own. When an object reference variable is passed as a parameter, the new object reference variable points to the same object instance as does the original object reference. No new object instance is created. That is, there remains only one object instance no matter the scope of either of the object reference variables. But how do we get this reference into an object reference variable in the first place?

An object reference variable generally comes into being without containing a valid reference (unless it is a parameter variable and is passed a valid reference as it is spawned). So it must be assigned a reference, either from another object reference variable or from a method that launches a suitable object instance and returns a reference to it. Only references to an object instance launched in a certain way can be assigned to an object reference variable. (That is, we can't use a reference to an object variable because it could easily go out of scope.) A little background information would be useful here...

Object Reference Basics

We will present a more thorough treatment of object references in a future article, but here is some basic information on how they are populated. Object classes in Omnis Studio version 4 and later contain a number of methods relating to their use with object reference variables. The method in which we are interested here is the $newref() method. When applied to an object class, this method launches a non-scoped instance of the object into the current task and returns a reference to that instance suitable for use in an object reference variable. We can populate the object reference variable in this way:

Calculate objRefVar as $clib.$objects.<objclassname>.$newref()

Parameters can be passed to the $construct() method of the object instance within the parentheses that follow the name of the method. Notice that we are not given options for assigning a name to the object instance. It certainly has a name (as do all instances within Omnis Studio), but we have no control over it because we generally access such instances through object reference variables. By default, Omnis Studio assigns the first instance of a given object class that is launched into a task by the $newref() method the same name as its class name (assuming that name has not yet been used). If subsequent instances of that same object class are launched into the same task instance while the first object instance still exists, each is given a name consisting of the class name, an underscore character and a randomly assigned integer (e.g., classname_256). But an object instance of that same class launched into a different task instance is again given the class name alone as its instance name. (I only point this out in this article because we will be using the Notation Inspector to view such object instances and I want to preempt any possible confusion that might arise.) So apparently uniqueness of object instance names is only required within a task and not within the entire application.

We can pass an object reference as a parameter to another object reference variable in the target method and that variable will also refer to the same object instance. If we need to pass that reference to a more "permanent" variable, like an instance variable, we can do so using a simple calculation:

Calculate ivObjRef as pvObjRef

We can also assign this reference to a cell in a list variable and all manner of other useful things, but those are subjects for another time. We have plenty of work to do here on our multiple task situation...

Setting Up the Task Superclass

So it would seem that all we need to do is to create the object reference variables we need in our task superclass, include code to populate them with an object instance (as outlined above) in the $construct() method of that task and then create all the task classes we need as subclasses of that superclass. Well, it's not quite that simple.

The trick is to create the new reference once in the first task to launch (the startup task) and then pass that reference to each new task that we instantiate. Setting up the task variables in the superclass is fine. That way we will have consistently named task variables guaranteed! But if we were to populate those variables using the $newref() technique in the superclass $construct() method, we would end up with multiple, independent instances of our object classes - just what we want to avoid! Does this mean this technique is another dead end? What options do we have?

There are at least two viable options. In the first case, we could simply override the $construct() method in each subclass and perform this operation only in our startup task. Then whenever we launch a new task instance, we can pass the necessary object references as parameters to the $construct() method of that instance. In the (overridden) $construct method of each task launched in this manner, we would then assign the reference held in this parameter to the appropriate task variable. Each of those will point back to the original instance launched in the startup task.

A second option is to also create a method in the task superclass named something like $setup() or $configure() and invoke that at some point within the $construct() method, passing appropriate parameters on to this subroutine. This is a little more involved, but it may appeal to some Omnis Studio programmers. That subroutine would then be overridden in the subclasses so that specific method lines could be included to address the configuration needs of each task.

I use a combination of these techniques in my own work. I use the first technique for establishing the object reference task variable(s) and the second technique for a number of other configuration operations. So my task superclass contains:

  • Task variables of object reference type (and possibly others) to which I want to have global access throughout the application
  • A $construct() method that performs basic, universal construction operations. This includes calling:
  • A $setup() method that is intended to be overridden in all subclasses, so it contains no code in the superclass
  • Other methods that may be universal or may need to be overridden in the subclasses (or may not be used at all in some applications), such as $destruct(), $control(), $activate(), $deactivate(), $suspend() and $resume()

Here is the code for the $construct() method of the task superclass (which I named taskmaster):

Do method $cinst.$setup
; other code as needed for specific application

Not much to it, really. The real tricks are in the parts that we override in the subclasses.

Setting Up the Task Subclasses

The first subclass to create is the startup task. We can simply use the default that was created when we first made our library and set its superclass property value to taskmaster (or whatever the name is of our task superclass). Any existing $construct() code remains untouched by this operation, which is fine since we would override that method anyway. We then just add a line to the beginning of that method (or to an appropriate position within the method if the beginning is not quite right) to perform the inherited bit. Our $construct() method would then look like this:

Do inherited
Install menu mainMenu
Set 'About...' method aboutCode/About Task Demo
; other code as needed

We next need to establish the object reference variable value and the object instance that it points to for each such task variable. For this task, we will do this in the $setup() method. The $setup method would then contain:

Calculate fn as $clib.$objects.commonMethods.$newref()
; other code as needed

Since the object instance is created in code executed within the startup task, that instance belongs to this task. This implies that the startup task must remain in existence for this object to continue to exist. I only mention this because I have seen "techniques" where the programmer launches another "main" task for the application and then closes the startup task. This is entirely unnecessary, but if a programmer feels that it is, then I would suggest not bothering to perform the operations here until the "main" task is established.

In most multiple task applications I create, I launch the various task instances for the application at startup. This way, the user only has to switch from one task to another and I don't have to repeatedly establish and destroy the resources for any specific task. But it is equally viable to launch a task when it is needed and then close it again when the user is finished working in that area. The technique I demonstrate here for launching them all at startup can be easily modified to accommodate other styles of programming.

Either the $construct() method or the $setup() method of the startup task can be used to launch the other task instances of the application. As always, there are two techniques that we can use to do this: the method command technique and the notational technique. Suppose that our application contains two tasks in addition to the startup task: memberTask and reportTask. Here is the code I would add to either the $construct() or $setup() method after the line(s) where the original object reference variable setup is performed:

Open task instance memberTask (fn)
Do $clib.$tasks.reportTask.$open(,fn)

Each of these method lines will launch a task instance with the same name as the class from which it was spawned. Notice that the first parameter of the notational method $open() is reserved for the name we wish to assign. This is one of the rare parameters that can be left absolutely empty in Omnis Studio. We can instead put an empty string (two quotes with nothing between them) as the value for this parameter and get the same effect. This tells Omnis Studio to use the class name as the name for the task instance. The second parameter for the notational method is the same as the first parameter for the command method. It is the first parameter that will be received by the $construct() method of the task instance.

This means that we must add a parameter variable of object reference type to the $construct() method of each task for each such variable we wish to pass from the startup task. (Our example here has just the one, but we could very well decide to have more in a real application.) I have used the name pFn for this parameter in each non-startup task (which makes copying and pasting code easier). The $construct() method of the memberTask might then look like this:

Do inherited
Calculate fn as pFn
Install menu memberMenu
Calculate $imenus.memberMenu.$local as kTrue

This assures that the reference to the original object instance is passed to our object reference variables in each task rather than establishing new object instances. The beauty of this is that any reference can be passed since they all point back to the same object instance. So it is not necessary to launch the other tasks all from the startup task, but only from a task that has received a reference that originated from there. The most important rule is that the startup task instance (or whichever task contains the object instance) must remain open for the reference to be valid.

Checking Our Handiwork

Once our application has been opened with this code in place, we should be able to verify that we have one, and only one, instance of our object class. We can do this in our development copy of Omnis Studio using the Notation Inspector.

It is important to understand that there is no "iObjects" group at the root level as there is for other types of instance. Neither can we drill down through the iTasks group to see the object instances contained within a task (which would only be the "non-scoped" instances we discussed in this article anyway, since those associated with object variables would not be contained so directly within a task instance). So where would we find this information?

We have to instead drill down through the $libs node, the library that contains the original object class, the $objects group within that library, the object class itself and finally the $insts group for that class. There we will see all the instances that are suitable for use with object reference variables. Note that we will not see object instances used with object variables in this listing. It may be that they are out of scope or it may be that they just aren't included in this prestigious group. Whatever the reason, we only see object instances launched by $newref() here.

Object instances in the Notation Inspector

This listing gives us a means of ascertaining whether we have inadvertently launched additional instances rather than passing references as we intended. It also lets us test to see whether we have successfully closed such an instance... but that is a topic for another time.

Next Time

In the next article, the final one on tasks for now, we will examine the process of closing a task instance. There are some interesting and useful twists to this process that make it worthy of closer scrutiny.

The third article in a series for MacTech magazine is nearing completion. The first two were in their March and May issues and there should be at least two more in June and July. Publication has lagged a bit from the real world calendar, but it is nice to be able to represent Omnis Studio in a trade publication of any sort - and these people have become very enthusiastic about Omnis Studio from reading my articles for editorial purposes.

 

 
© 2005 Copyright of the text and images herein remains with the respective author. No part of this newsletter may be reproduced, transmitted, stored in a retrieval system or translated into any language in any form by any means without the written permission of the author or Omnis Software.
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7™ is a trademark of Omnis Software UK Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.

Search Omnis Developer Resources

 

Hit enter to search

X