![]() |
Technical Resources at AEGISArticlesSynchronizing Threads in using Server Push Tip By: Mike Barlotta - Appeared in PBDJ Jan. 1999 |
|
In the article "Building Multithreaded Applications using the Shared Object" which appeared in the PBDJ (Vol 5 Issue 8) we examined threads and explored how the shared object could be used to create multithreaded applications in PowerBuilder. In this article we will build a multithreaded application and examine how to synchronize the threads using server push. Our multithreaded application will run two counters simultaneously. Two threads will be started, one for each counter. They will be synchronized with the primary thread to display their progress on a window. In addition the user will have the option of retrieving data from a database while both counters are running. Calling Thread Methods - Revisited Shared objects use the single threaded apartment model so all functions invoked on a shared object are placed into a queue. As explained in the last article, this prevents other threads from having concurrent access to the object and prevents corruption of data within the thread itself. When calling functions on a separate thread the methods should be invoked asynchronously so that program logic can be run in parallel as in the following example: inv_thread.POST of_DoSomeThing() this.of_DoSomeThingElse() Calling a function using the POST keyword will invoke the method asynchronously. The calling script will place the request on the queue of the shared object and than continue processing in the calling script immediately rather than wait for the function that was called to complete processing. However when calling a method asynchronously the return value is not made available to the calling script preventing the function call from being used as part of an expression. Trying to get a result back from a posted method call like the statement below will generate a compiler error. li_result = inv_thread.POST of_DoSomeThing() Methods that are being invoked asynchronously cannot accept arguments that are passed by reference. An attempt to call a method on a shared object and pass an argument by reference will generate the following run-time error: ‘Reference parameters can not be passed to an asynchronous shared / remote object.’ Since we cannot get a return value from a method invoked using POST and cannot pass arguments by reference, how can the calling script determine if the function has run successfully or get any information back from a separate thread? In order to find out if a method has completed processing or get results back from a method running in a separate thread that was called asynchronously we will need to use the server push technique. How Server Push Works PowerBuilder 6.0 introduces the capability of passing object references between threads and processes. This new feature can be used to implement a technique that is called server push. The ability to pass object references between threads and implement server push was added to PowerBuilder to allow a DPB server to send information to a client application and invoke processing on the client. The server push concept is simple, the server can pass messages to a client application by invoking methods on a client-side object. The client-side object methods that are called by the server can allow the client application to take appropriate action based on information that is received. In order for the server to have access to a client-side object the client application needs to pass an object reference pointing to a local object to the server. We can use this same technique between threads in a multithreaded application to notify one thread when some processing has been completed in a separate thread and thereby synchronize the processing. We do this by passing an object reference from one thread to another. In the examples used in this article we will be passing an object reference from the primary thread to a shared object. As the code sample below illustrates, this is very easy to do. // Primary Thread (Window Open event) // Create the synch object inv_notify = CREATE n_notify // Create a thread SharedObjectRegister("n_pbdj_thread", "SO_THREAD1") // Get a reference to the shared object SharedObjectGet("SO_THREAD1", inv_thread) // Pass the thread an object reference to the synch object inv_thread.of_PassNotify(inv_notify) After running the code sample we have a situation as illustrated in figure 1.
Figure 1 Overview of Server Push Before continuing let's review the code sample and make some important points. The object reference that is passed to the shared object must point to a valid object instance in the primary thread. Once the shared object has a reference to an object, it can call methods and access attributes on the object as if the object were created locally. All requests that are made against the object reference are sent to the object instance in the primary thread. It is important for the primary thread not to destroy the local object that has been passed to the shared object. If the shared object attempts to use the object and it is not instantiated a null object reference error will be generated. This technique is being shown using the primary thread and a shared object. It can also be used between two shared objects. The shared object can store the object reference in an instance variable and use it as required to notify the primary thread of its processing status. The code sample below illustrates how the shared object script can use the object reference. // of_PassNotify // Arguments: // n_notify anv_notify inv_notify = anv_notify inv_notify.POST of_Notify("Got the object reference") The code sample assumes that the object notify passed in has a method of_Notify that accepts a string argument. The shared object can call methods using the object reference both synchronously and asynchronously. The method called by the shared object is placed on a queue in the primary thread. This is the same way the shared object handles a method call from the primary thread. Function calls made by the shared object should be invoked asynchronously to avoid locking up the thread while it is waiting for the method in the primary thread to complete processing. Building a Basic Notify Object The notify object we will build in this article will be generic enough that it can be used in most situations. Our basic notify object will define a function of_notify that will be able to accept a simple string, number, or nothing at all from the server application. To implement a specialized notify object simply inherit from the base class and add the appropriate functionality. We will declare instance variables to store any data the server returns. protected string is_msg protected long il_number We will overload the function of_notify to handle getting back a string, a number, and nothing at all. integer of_notify(value long al_number) il_number = al_number RETURN 0 integer of_notify(value string as_msg) is_msg = as_msg RETURN 0 integer of_notify() RETURN 0 The idea behind the notify object is to provide a simple object that can be used in most cases. Any special processing that needs to be handled as a result of the threads being synchronized should be handled by a specialized object that can be called from the notify object. To handle the specialized object that the notify object may need to interact with we will need an instance variable to hold that object. Since this object could be a window, a single line edit, or another non-visual object we will declare it to be of type powerobject.
protected powerobject ipo We will also need to create a method to allow objects to register themselves with the notify object. integer of_setpo(value powerobject apo) ipo = apo RETURN 0 We will use this technique in are sample application to pass a custom single line edit object on a window to the notify object. This will allow the notify object to receive messages from our counter object that will be running in a separate thread and display them in the single line edit in the primary thread. The code to create the thread and register the single line edit is shown below. // Window open event // Create Notify objects inv_notify1 = CREATE n_notify // Register SLE with the notify object inv_notify1.of_SetPO(sle_thread1) // Create Thread/Shared Object IF SharedObjectRegister("n_thread", "SO_THREAD1") = Success! THEN IF SharedObjectGet("SO_THREAD1", inv_thread1) = Success! THEN // Pass notify object inv_thread1.of_PassNotify(inv_notify1) ELSE MessageBox("Error", "Could not get reference to Thread") END IF ELSE MessageBox("Error", "Could not start Thread") END IF Our shared object will call the function of_notify as the counter runs passing it the number it is on. Our specialized of_notify function will receive this information and call a method on the single line edit passing it the number so that it can be displayed. The code for the specialized of_notify function is shown below. // of_Notify integer of_notify(value long al_number) IF IsValid(ipo) THEN ipo.Dynamic of_SendMsg(al_number) END IF RETURN 0 Creating the Shared Object In order to build a multithreaded application we will need to create the object that will be registered as a shared object to handle the processing in a separate thread. Any custom class user object and most non-visual standard objects provided by PowerBuilder can be registered as a shared object. The shared object is typically a custom class user object (see Figure 2). To create the user object, open the user object painter and select the custom class option.
Figure 2 Add an instance variable to hold the notify object. It is declared as follows: n_notify inv_notify Our thread object will run a simple counter and notify the primary thread of its progress. To do this we will need to create two functions. One to run the counter and another to receive the notify object from the primary thread. The declarations for the functions are shown in figure 3.
Figure 3 Functions in the Thread Object The function of_DoSomeThing will run a counter and returns its progress. The code listing is shown below. // of_DoSomeThing long i FOR i = 1 TO 5000 IF IsValid(inv_notify) THEN inv_notify.of_notify(i) END IF NEXT RETURN 1 The function of_PassNotify will receive the object reference to the object n_notify and store it in the instance variable. The code listing is shown below. // of_PassNotify IF IsValid(anv_notify) THEN inv_notify = anv_notify RETURN 1 ELSE RETURN -1 END IF The sample application interface is shown in figure 4. It implements two threads running counters as well the ability to search for employees by last name in the PowerBuilder demo database.
Figure 4 Multithreaded Application interface The start button for each thread simply calls the method of_DoSomeThing. inv_thread1.POST of_DoSomeThing() A final review The code samples shown here are intertwined so let's outline the steps to take in building a multithreaded application.
Conclusion This article has covered the basics behind synchronizing threads in a multithreaded application using server push. The code samples here do not show how to build the entire application but cover the techniques that can be used to build it. The source code for the sample application can be found at AEGIS Technical Resources in the Technical Forum under the PowerBuilder Zone. |
|