Threading


Additional assembly information: Contents, Object Model Diagram
The Threading namespace contains classes for performing asynchronous operations in ArcGIS Explorer. The ESRI.ArcGISExplorer.Threading.BackgroundWorker class provides a similar programming experience to using the System.ComponentModel.BackgroundWorker class (part of the .NET framework).

See the following sections for more information about this namespace:

When to consider threading

Some operations can take a long time to complete. For example, calling a Web service, saving, or downloading files or images, accessing a remote database, or performing particularly intensive local calculations. To avoid the ArcGIS Explorer user interface (UI) becoming unresponsive during longer operations, use the threading functionality in ArcGIS Explorer to perform operations asynchronously. Using a threading solution allows multiple operations to occur simultaneously, allowing an operation to complete on a separate worker thread while the user continues to interact with the UI on the main UI thread.

A full discussion of threading is outside the scope of this software development system (SDK). For more information, see the Microsoft Developers Network (MSDN) Web site.
Not all operations are suitable for asynchronous execution. If the user can continue using the application before the operation completes, this could be an appropriate operation to perform asynchronously; for example, writing out to a large file that is not needed by the application. Not all classes in the application programming interface (API) can be accessed in the asynchronous use pattern described in this topic; for example, adding a layer must be performed synchronously as it changes the contents of the map display in the UI.

Performing operations with BackgroundWorker on a worker thread

Use the ESRI.ArcGISExplorer.Threading.BackgroundWorker class to perform operations on a worker thread in ArcGIS Explorer. This class inherits from the .NET Framework System.ComponentModel.Component class and provides a similar (but not identical) programming experience to using the System.ComponentModel.BackgroundWorker class, which is part of the .NET Framework.
In this topic, if the type BackgroundWorker is not fully-qualified, this refers to the ESRI.ArcGISExplorer.Threading class (not the .NET Framework class).
ArcGIS Explorer classes must be used within single threaded apartments (STAs); however, the .NET Framework BackgroundWorker uses a managed thread pool that executes its threads in multithreaded apartments (MTAs). This disparity in requirements means that the .NET Framework BackgroundWorker class is not suitable to use with ArcGIS Explorer objects.

Do the following to execute a time-consuming operation on a worker thread:
  1. Create a BackgroundWorker object.
  2. Define an event handler for the DoWork event and associate it with the event on the BackgroundWorker object you created.
  3. Add code to the DoWork event handler to perform the worker thread operation.
  4. Call the RunWorkerAsync method on the BackgroundWorker object to start the worker thread operation.

The following code example shows a Button add-in class that when clicked, allows a user to track a Point location on the MapDisplay. This Point location is serialized and passed to a worker thread using a BackgroundWorker. The BackgroundWorker then saves the Point information to a file.
[C#]
public class SimpleButton: ESRI.ArcGISExplorer.Application.Button
{
    // References to the MapDisplay and BackgroundWorker are created, 
    // and therefore only used, on the UI thread.
    MapDisplay _md;
    BackgroundWorker _bgWorker;
    // Constructor is called on the UI thread.
    public SimpleButton()
    {
        _md = Application.ActiveMapDisplay;

        // Create an Explorer BackgroundWorker component and hook 
        // up the DoWork event handler.
        _bgWorker = new BackgroundWorker();
        _bgWorker.DoWork += new DoWorkEventHandler(DoWork);
    }
    // OnClick is called on the UI thread.
    public override void OnClick()
    {
        // Access the active display and track a Point.
        Point ptOnUIThread = _md.TrackPoint();
        if (!_bgWorker.IsBusy)
        {
            // Trigger the DoWork event handler.
            _bgWorker.RunWorkerAsync(ptOnUIThread.ToXmlString());
        }
    }
    // DoWork is called on a worker thread.
    private void DoWork(object sender,
                        ESRI.ArcGISExplorer.Threading.DoWorkEventArgs args)
    {
        Point ptOnBgThread = Point.CreateFromXmlString((string)args.Argument);

        // Perform some work on the background thread, such as
        // saving the clicked locations to a file.
        string filename = System.IO.Path.GetTempFileName();
        System.IO.StreamWriter sw = System.IO.File.CreateText(filename);
        sw.Write(ptOnBgThread.ToString());
        sw.Close();
    }
}
[VB.NET]
Public Class SimpleButton
    Inherits ESRI.ArcGISExplorer.Application.Button
    ' References to the MapDisplay and BackgroundWorker are created,
    ' and therefore only used, on the UI thread.
    Private _md As MapDisplay
    Private _bgWorker As ESRI.ArcGISExplorer.Threading.BackgroundWorker
    ' Constructor is called on the UI thread.

    Public Sub New()
        _md = ESRI.ArcGISExplorer.Application.Application.ActiveMapDisplay
        ' Create an Explorer BackgroundWorker component and hook
        ' up the DoWork event handler.
        _bgWorker = New ESRI.ArcGISExplorer.Threading.BackgroundWorker()
        AddHandler _bgWorker.DoWork, AddressOf DoWork
    End Sub

    ' OnClick is called on the UI thread.
    Public Overrides Sub OnClick()
    ' Access the active display and track a Point.
    Dim ptOnUIThread As ESRI.ArcGISExplorer.Geometry.Point = _md.TrackPoint()
    If (Not _bgWorker.IsBusy) Then
        ' Trigger the DoWork event handler.
        _bgWorker.RunWorkerAsync(ptOnUIThread.ToXmlString())
    End If
End Sub

' DoWork is called on a worker thread.

Private Sub DoWork(ByVal sender As Object, ByVal args As ESRI.ArcGISExplorer.Threading.DoWorkEventArgs)
    Dim ptOnBgThread As ESRI.ArcGISExplorer.Geometry.Point = ESRI.ArcGISExplorer.Geometry.Point.CreateFromXmlString(CStr(args.Argument))
    ' Perform some work on the background thread, such as
    ' saving the clicked locations to a file.
    Dim filename As String = System.IO.Path.GetTempFileName()
    Dim sw As System.IO.StreamWriter = System.IO.File.CreateText(filename)
    sw.Write(ptOnBgThread.ToString())
    sw.Close()
End Sub

End Class
In addition to this basic workflow, you can also do the following:
  • Pass information to the DoWork event handler by using the overloaded RunWorkerAsync method, which allows you to specify an argument parameter. 
  • Use the ProgressChanged event and BackgroundWorker.ReportProgress method to update the user with progress information; generally, this is used to show and update a non-modal progress dialog box. At the most, the event will only be triggered once every half second.
  • Use the RunWorkerCompleted event and the Result property of the DoWorkEventArgs parameter in the DoWork method to send information about the result of the worker thread operation back to the UI thread; generally, this is used to report a result to the user or otherwise, update the UI.
Each BackgroundWorker can only deal with a single asynchronous operation at a time—the BackgroundWorker does not queue up calls to RunWorkerAsync. If another call to RunWorkerAsync is made during an existing asynchronous operation, an InvalidOperationException is thrown. If you need to perform a second asynchronous operation while the first is still running, you can create a second BackgroundWorker object to use; ArcGIS Explorer manages a small pool of threads for BackgroundWorkers to use this way.

Serializing ArcGIS Explorer objects between threads

There are limitations on the kind of operations that can be executed on a worker thread. ArcGIS Explorer objects are not thread-safe and must only be used on the thread, on which they were created. If you attempt to use an ArcGIS Explorer object on a different thread to the one it was created on, an ESRI.ArcGISExplorer.ThreadingException will be thrown. For example, a Layer object can be created and connected to a Layer on a worker thread; however, it cannot be added to the current map from the worker thread. This situation is similar to that found in the .NET Framework System.Windows.Forms.Form class.
However, many ArcGIS Explorer objects can be serialized to a worker thread and back again, creating new objects with the same state on a different thread to work with. The ToXmlString and CreateFromXmlString methods, found on the Geometry, Graphic, MapItem, Symbol, and Viewpoint classes help you perform the serialization and deserialization. See the following code example:
[C#]
// To serialize the selected MapItem, first get the object, then use the 
// ToXmlString helper method. This code assumes a MapItem is selected.
string selectedItemXml = Application.SelectedItems[0].ToXmlString();
// To deserialize, use CreateFromXmlString.
MapItem newItem = MapItem.CreateFromXmlString(selectedItemXml);
[VB.NET]
' To serialize the selected MapItem, first get the object, then use the
' ToXmlString helper method. This code assumes a MapItem is selected.
Dim selectedItemXml As String = ESRI.ArcGISExplorer.Application.Application.SelectedItems(0).ToXmlString()
' To deserialize, use CreateFromXmlString.
Dim newItem As MapItem = MapItem.CreateFromXmlString(selectedItemXml)
For more information, see How to serialize ArcGIS Explorer objects between threads.
Value types can also be passed between the UI and worker threads in this manner. Reference types that are thread safe can also be passed in this manner (ArcGIS Explorer objects are not thread safe).

Best practices for programming with a BackgroundWorker

  • ArcGIS Explorer objects can only be accessed on the thread they were created. For example, a Layer object can be created and connected to a Layer on a worker thread; however, it cannot be added to the current map (which resides on the UI thread) from the worker thread. You can set up the Layer on a worker thread and serialize it, passing it back to the UI thread as a result, then deserialize and add to the map in the WorkerCompleted event handler.
  • Before calling RunWorkerAsync on a specific instance of a BackgroundWorker, check the IsBusy property; do not attempt to start a new worker thread operation if the BackgroundWorker is already working.
  • Do not store member variables and access them from different threads. This type of usage is possible but is considered an advanced usage and out of the scope of this topic; remember that ArcGIS Explorer objects are still subject to the limitation of not being thread-safe. If required, refer to the thread synchronization .NET Framework documentation on the MSDN Web site.
  • Use the ToXmlString and CreateFromXmlString helper methods to serialize and deserialize ArcGIS Explorer objects.
  • Do not attempt to perform UI work on the worker thread. Do not show UI components, such as forms. If you need to show UI components during an asynchronous operation, communicate to the UI through the arguments of the ProgressChanged and RunWorkerCompleted events.
  • Since ArcGIS Explorer has a limited number of threads available for processing BackgroundWorker operations, do not create worker processes that run for the  application's life span as this monopolizes one of the threads permanently. Instead, consider alternatives; you might want to create an extension that works regularly triggered by a timer event.
  • If an exception is thrown in the worker thread and not caught within that scope, then after the DoWork event handler completes, the error will be accessible from the RunWorkerCompletedEventArgs.Error property; therefore, it is good practice to always handle the RunWorkerCompleted event and check for any errors in your worker thread.
  • Since the ProgressUpdated event will only be triggered at a minimum of once every half second, any calls to ReportProgress that are more frequent than this limit will be ignored; therefore, you might want to review any code that calls ReportProgress in quickly completing loops.

See Also:

Vehicle Tracker extension