Multi-entrance coding / Concurrency

Polar Script is fully multi-entrance. This means that in a multi-tasking environment - such as Polar Studio - multiple script actions for multiple requests can run at the same time. This is no issue for normal procedures, for all variables will be specific and unique for the calling process. However, if you are using global variables or you are sharing class-objects over processes, you can run in to specific problems.

Dim mSettingsCache As Collection
Dim mSettingsCache_LastReadDateTime As Date

Public Function GetSettingFromCache(pContext, pSettingName As String) As String
   'Force a re-read of the settings each 5 minutes
   If mSettingsCache Is Nothing Or mSettingsCache_LastReadDateTime < SystemTime - TimeSerial(0, 5, 0) Then
       Set mSettingsCache = New Collection
       
       Using pContext.OpenDynamicRecordSet("SELECT Name, Value FROM Setting")
           Do While Not .Eof
               mSettingsCache.Add .Fields("Value").Value, .Fields("Name").Value
               .MoveNext
           Loop
       End Using
       
       mSettingsCache_LastReadDateTime = SystemTime
   End If
   
   Return mSettingsCache.TryGet(pSettingName, "")
End Function

In the example above, multiple routines may make use of the same cache with settings (mSettingsCache). This could lead to a situation where two entrances are filling the cache at the same time or one entance is reading the cache while the other has just cleared it.

To solve this concurrency problem, we can make use of a Lock, as shown in the modified code below. This lock ensures the code within the Lock is only entered once at a time.

Dim mSettingsCache As Collection
Dim mSettingsCache_LastReadDateTime As Date

Public Function GetSettingFromCache(pContext, pSettingName As String) As String
   Using Lock
       'Force a re-read of the settings each 5 minutes
       If mSettingsCache Is Nothing Or mSettingsCache_LastReadDateTime < SystemTime - TimeSerial(0, 5, 0) Then
           Set mSettingsCache = New Collection
           
           Using pContext.OpenDynamicRecordSet("SELECT Name, Value FROM Setting")
               Do While Not .Eof
                   mSettingsCache.Add .Fields("Value").Value, .Fields("Name").Value
                   .MoveNext
               Loop
           End Using
           
           mSettingsCache_LastReadDateTime = SystemTime
       End If
       
       Return mSettingsCache.TryGet(pSettingName, "")
   End Using
End Function

If the global variable mSettingsCache is used on more than one place and you want to implement 'cross-locking', you can make up your own unique name for this lock and specify this name when aquiring the lock:

Dim mSharedVariable

Function A
   Using Lock("MySharedLockName")
       'Use mSharedVariable here
   End Using
End Function

Function B
   Using Lock("MySharedLockName")
       'Use mSharedVariable here
   End Using
End Function

Notes

Starting a task in the background

If you want, you can start a task in the background using the CallAsync statement. The background task will run while the main thread will continue running at the same time. You can use background tasks for example to do complex calculations and you do not want the main process to be waiting for this.

When calling the background-task, you may want to set up pre-cautions that this task is not started multiple times overlapping doing the same task.

The CallAsync statement cannot capture a returned value because it does not wait for the background task to complete.

Public Function MainFunction(pContext)
   CallAsync BackgroundTask(pContext)
End Function

Private Function BackgroundTask(pContext)
   Using Lock("", 500) 'Using a lock to prevent concurrent entrance. Waiting max 500 ms (half a second) to aquire the lock
       'Do complex calculations here
   End Using
End Function

See also: