[ArcGIS Pro secondary development] (31): Multithreading in ArcGIS Pro

ArcGIS Pro is significantly different from the old ArcGIS desktop application in that it uses a multi-threaded architecture that can effectively take advantage of multi-core CPUs. This makes the performance of secondary development tools better, but also brings more difficulties and challenges to the development work.

1. Problems needing attention in multi-threading

  • In general, to ensure a responsive user experience, the GUI thread must be able to take input from the user and output it smoothly. This means that the GUI thread and your main worker thread must execute asynchronously on different threads, the GUI thread should not perform other work, and the two cannot interfere with each other.
  • When performing work on a background thread, a logically consistent and informative user interface must be presented to the user. According to the operation being performed in the background, necessary operation instructions to the user interface and appropriate feedback should be provided. If a long-running background operation is logically cancelable, a cancel option should be provided.
  • If there is a conflict between operations, they should not be executed simultaneously, but should be executed in an appropriate logical order. For example, operations on maps cannot be performed while the project containing the map is still loading. Most operations initiated by user interaction are logically order dependent and should be executed serially.
  • During asynchronous execution, pay attention to multiple threads accessing the same variable, file, element at the same time, or the wrong access sequence, for example, copying One element, then delete the original element, if the order is reversed, delete it first and there is no way to copy it. This problem is almost non-existent in single-threaded, because the order is obvious at a glance. But once there are more threads and the logic is complex, it is inevitable that there will be mistakes and omissions.

Second, ArcGIS Prointernal threading model

Of course, we don’t need to study so deeply in the secondary development of ArcGIS Pro. Because the infrastructure of ArcGIS Pro SDK has reduced the complexity of the code.

In most cases, we only need to deal with two threads: [User Interface Thread] and a single [Dedicated Worker Thread] provided by the application. Internally, ArcGIS Pro uses numerous threads for rasterization, graphics rendering, data loading, and select geoprocessing algorithms that exploit parallelism to speed up calculations. But these threads are completely internal threads and have nothing to do with our external development.

When we call a method in the public API, it may be split into multiple internal methods for implementation, and the fragments are delegated to one or more dedicated internal threads.

Three, Task and Task asynchronous mode

Methods in the ArcGIS Pro SDK fall into three categories:

1. An asynchronous method that can be called on any thread. This type of method will add an [async] modifier, usually returning a task. But if a variable is returned, an asynchronous call is required.

2. Synchronous methods that are called only on worker threads.

3. A synchronous method called only on the GUI thread. These types of methods are often associated with WPF.

.NET introduces the most important [async] and [await] language features.

The [async] modifier is used to mark the method so that the compiler knows that the method is asynchronous.

[await] is used to call the method asynchronously, and then force the calling thread to automatically return to the next line and continue execution after the asynchronous operation is completed.

 private async void Button_Click(object sender, RoutedEventArgs e)
  {
    double computedValue = await Task. Run<double>(()=>
    {
      double result = 42.0;
      return result;
    });
    // The following MessageBox task will be executed only after the content after the above await is executed
    MessageBox. Show(String. Format("Result was {0}", computedValue. ToString()));
  }

Taking the above code as an example, the MessageBox.Show() method will not continue until the asynchronous method after await is executed.

The output result is [Result was 42].

Fourth,Use of QueuedTask

Although Task can run in any Add-in code, the ArcGIS Pro SDK framework provides a better QueuedTask. In fact, we basically only use this QueuedTask in development.

This is a custom task scheduler, which is used to schedule the task of the synchronization method in ArcGIS Pro SDK by using 【QueuedTask.Run】. This scheduling is essentially a queuing method that allows multiple tasks to be executed in an orderly manner.

The sample code is as follows:

 Task t = QueuedTask. Run(()=>
  {
    // call synchronous SDK methods here
  });

There are many advantages to using the QueuedTask class in the ArcGIS Pro SDK over the Task class.

1. In the task body, developers can access various objects of ArcGIS Pro, such as maps, layers, features, etc. Access to these objects is thread-safe because QueuedTask automatically manages communication with the main thread.

2. If the task needs to update UI elements, such as modifying the map view or displaying progress information, developers can use the Dispatcher property of QueuedTask to wrap the update operation in the Dispatcher.BeginInvoke method. This ensures that update operations are performed on the main thread, avoiding thread conflicts.

3. If the task needs to be canceled, the developer can call the QueuedTask.Cancel method, which will trigger the cancellation of the task. A canceled task exits as soon as possible after cancellation, and any necessary cleanup can be done in the task body.

4. If an exception occurs during task execution, QueuedTask will capture the exception and pass it to the developer for proper handling. Developers can use try-catch blocks in the task body to catch and handle exceptions.

In summary, QueuedTask in the ArcGIS Pro SDK provides a convenient and powerful mechanism for executing tasks in the background and safely interacting with the UI thread of an ArcGIS Pro application. Its advantages include multithreading support, UI thread safety, cancelability and exception handling, etc. By properly using QueuedTask, developers can implement efficient and reliable custom functions and extensions.

Execution order of QueuedTask tasks

Here are two examples to illustrate the execution order of QueuedTask tasks.

 MessageBox. Show("1");
            await QueuedTask. Run(() =>
            {
                MessageBox. Show("2");
                MessageBox. Show("3");
            });
            MessageBox. Show("4");

Take the code above as an example, which is a normal, reasonably asynchronous execution. The display order of MessageBox is [1, 2, 3, 4].

But if the content of await is written as a method and called synchronously, the situation is different:

 MessageBox. Show("1");
        Spa();
        MessageBox. Show("4");

        public static async void Spa()
        {
            await QueuedTask. Run(() =>
            {
                MessageBox. Show("spa_1");
                MessageBox. Show("spa_2");
            });
        }

Execute the above code, and the display order of MessageBox is [1, (spa_1, spa_2), 4]. It has become a parallel execution. Obviously, this execution method is out of order and it is easy to cause errors.

Unfortunately, I’ve made many of these mistakes so far.

Because I am used to writing some common processes as methods and calling them, many asynchronous methods are written, and they are directly called synchronously when called. There were a lot of BUGs, and it was only after calling around that I found out that it was the reason for asynchronous parallelism.

Make modifications on the basis of the above code, change the custom method to asynchronous execution in the main code, and change the internal method to synchronous execution:

 MessageBox. Show("1");
        await QueuedTask. Run(() =>
        {
            Spa();
        });
        
        MessageBox. Show("4");

        public static async void Spa()
        {
            MessageBox. Show("spa_1");
            MessageBox. Show("spa_2");
        }

As a result of the operation, the process changed back to serial execution, which barely solved the parallel problem.

Basic too Weakness is still not enough, which is the main reason for going back to make up the foundation and recording this article.