How to run an asynchronous gp service from ArcGIS Pro SDK?

2833
8
Jump to solution
04-09-2020 12:20 PM
JamesGough
Regular Contributor

I am new to the ArcGIS Pro SDK. I am attempting to run a Python toolbox tool that I published to ArcGIS Server as a GPServer. The task is asynchronous (esriExecutionTypeAsynchronous).

I have tried running the tool using await Geoprocessing.ExecuteToolAsync(), this successfully submits the job, and the job is successful. However, my code does not actually wait until the job finishes. It just submits it, and then continues.

Is there way to submit a job and check its status until completion using the Geoprocessing namespace or any other function in the SDK? Or do I just need to do this using the ArcGIS Server REST API?

The gist of my code:

protected override async void OnClick()
 {
   // Setup Progress dialog
   var progDlg = new ProgressDialog("Running Tool", "Cancel", 100, true);
   progDlg.Show();
   var progSrc = new CancelableProgressorSource(progDlg);
   // Tool parameters
   string tool_path = @"path to my tool on server using a .ags file";
   string flags_fc = @"path to a feature class";
   string barriers_fc = @"path to a feature class";
   string skip_locations = @"path to a feature class";
   var parameters = Geoprocessing.MakeValueArray(flags_fc, barriers_fc, skip_locations);
   // Run the Tool
   var result = await Geoprocessing.ExecuteToolAsync(tool_path, parameters, null, new 
       CancelableProgressorSource(progDlg).Progressor, GPExecuteToolFlags.Default);
   progDlg.Hide();
   Geoprocessing.ShowMessageBox(result.Messages, "GP Messages", result.IsFailed ? 
   GPMessageBoxStyle.Error : GPMessageBoxStyle.Default);
 }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Thanks,

James

0 Kudos
1 Solution

Accepted Solutions
GKmieliauskas
Esri Regular Contributor

Hi James,

Have you tried like this:

                    var parameters = Geoprocessing.MakeValueArray(fullSpec, fieldName);
                    var cts = new CancellationTokenSource();
                    var results = Geoprocessing.ExecuteToolAsync("management.DeleteField", parameters, null, cts.Token,
                        (eventName, o) =>
                        {
                            System.Diagnostics.Debug.WriteLine($@"GP event: {eventName}");
                            if (eventName == "OnMessage")
                            {
                                System.Diagnostics.Debug.WriteLine($@"Msg: {o}");
                            }
                        });
If you want to wait until your job finishes you can do like this:
               var parameters = Geoprocessing.MakeValueArray(sInputPath);
                var gpResult = Geoprocessing.ExecuteToolAsync("Delete_management", parameters, null, CancelableProgressor.None,
                        GPExecuteToolFlags.None);
                gpResult.Wait();
                return !gpResult.Result.IsFailed;
Or you can combine both.

View solution in original post

0 Kudos
8 Replies
GKmieliauskas
Esri Regular Contributor

Hi James,

Have you tried like this:

                    var parameters = Geoprocessing.MakeValueArray(fullSpec, fieldName);
                    var cts = new CancellationTokenSource();
                    var results = Geoprocessing.ExecuteToolAsync("management.DeleteField", parameters, null, cts.Token,
                        (eventName, o) =>
                        {
                            System.Diagnostics.Debug.WriteLine($@"GP event: {eventName}");
                            if (eventName == "OnMessage")
                            {
                                System.Diagnostics.Debug.WriteLine($@"Msg: {o}");
                            }
                        });
If you want to wait until your job finishes you can do like this:
               var parameters = Geoprocessing.MakeValueArray(sInputPath);
                var gpResult = Geoprocessing.ExecuteToolAsync("Delete_management", parameters, null, CancelableProgressor.None,
                        GPExecuteToolFlags.None);
                gpResult.Wait();
                return !gpResult.Result.IsFailed;
Or you can combine both.
0 Kudos
JamesGough
Regular Contributor

Hi Gintautas,

Thanks for the response!

So if I understand, the first option you showed uses a callback function that should be called when the job finishes.

The second option uses the Task.Wait method to wait until the task finishes. Is this blocking or does it automatically spawn a new thread?

thanks,

James

0 Kudos
GKmieliauskas
Esri Regular Contributor

HI James,

More info about first option:

ArcGIS Pro 2.5 API Reference Guide 

0 Kudos
JamesGough
Regular Contributor

Thanks that is helpful, the Pro SDK documentation is a little confusing to navigate

0 Kudos
JamesGough
Regular Contributor

The GPToolExecuteEventHandler Delegate options seems to be working, thanks!

0 Kudos
Flayner
Occasional Contributor

Hi, @JamesGough 

Could you share how your code looked after implementing the solution?

0 Kudos
JamesGough
Regular Contributor

@FlaynerThis was quite a while ago, but i think this is the right code:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ArcGIS.Core.CIM;
using ArcGIS.Core.Data;
using ArcGIS.Core.Geometry;
using ArcGIS.Desktop.Catalog;
using ArcGIS.Desktop.Core;
using ArcGIS.Desktop.Core.Geoprocessing;
using ArcGIS.Desktop.Editing;
using ArcGIS.Desktop.Extensions;
using ArcGIS.Desktop.Framework;
using ArcGIS.Desktop.Framework.Contracts;
using ArcGIS.Desktop.Framework.Dialogs;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using Newtonsoft.Json;

namespace DAC_Calculator
{
    internal class RunGpTool : Button
    {
        protected override async void OnClick()
        {
            // Tool parameters
            // Use .ags file to connect to arcgis server
            var projectHomeFolder = Project.Current.HomeFolderPath;
            var projGDBPath = Project.Current.DefaultGeodatabasePath;

            var featureClassNames = Module1.Current.DACInputFcNames;
            string tool_path = projectHomeFolder + @"\site.ags\DACTest/DACTrace2\DACTrace";
            string flags_fc = projGDBPath + @"\" + featureClassNames[0];
            string barriers_fc = projGDBPath + @"\" + featureClassNames[1];
            string skip_locations = projGDBPath + @"\" + featureClassNames[2];
            var parameters = Geoprocessing.MakeValueArray(flags_fc, barriers_fc, skip_locations);

            foreach(string name in featureClassNames)
            {
                // Check that feature class exists
                if (!await Module1.FeatureClassExistsAsync(name))
                {
                    // if not, create it
                    await Module1.CreateFeatureClass(name, Module1.EnumFeatureClassType.POINT);
                }
            }

            // Set up progress dialog
            ProgressDialog progDialog = new ProgressDialog("Running DAC", false);
            var status = new ProgressorSource(progDialog);
            progDialog.Show();

            // run tool with callback
            var cts = new CancellationTokenSource();
            var results = Geoprocessing.ExecuteToolAsync(tool_path, parameters, null, cts.Token,
                async (eventName, o) =>  // implement delegate and handle events, o is message object.
                {
                    System.Diagnostics.Debug.WriteLine($@"GP event: {eventName}");
                    if (eventName == "OnMessage")
                    {
                        System.Diagnostics.Debug.WriteLine($@"Msg: {o}");
                    }

                    switch (eventName)
                    {
                        case "OnValidate": // stop execute if any warnings
                            if ((o as IGPMessage[]).Any(it => it.Type == GPMessageType.Warning))
                                cts.Cancel();
                            break;

                        case "OnProgressMessage":
                            if ((o as string) == "Updating...")
                            {
                                status.Progressor.Message = "Job Completed, Updating Map...";
                            }
                            else
                            {
                                string msg = string.Format("{0}: {1}", new object[] { eventName, (string)o });
                                System.Windows.MessageBox.Show(msg);
                            }
                            cts.Cancel();
                            break;

                        case "OnProgressPos":
                            string msg2 = string.Format("{0}: {1} %", new object[] { eventName, (int)o });
                            System.Windows.MessageBox.Show(msg2);
                            cts.Cancel();
                            break;

                        case "OnMessage":
                            IGPMessage msg3 = o as IGPMessage;
                            string messageText = msg3.Text;
                            // MessageBox.Show(messageText, "DAC Tool");
                            switch (messageText)
                            {
                                case "Submitted.":
                                    status.Progressor.Message = "Job Submitted to server.";
                                    break;

                                case "Executing...":
                                    status.Progressor.Message = "Job executing on server...";
                                    break;
                            }
                            break;

                        case "OnBeginExecute":
                            // MessageBox.Show("Beginning Execution", "DAC Tool");
                            break;

                        case "OnEndExecute":
                            progDialog.Hide();


                            IGPResult res = o as IGPResult;
                            // IGPResult.Parameters contains all tool parameters (both input and output)
                                // Tuple.Item1 - name
                                // Tuple.Item2 - datatype
                                // Tuple.Item3 - value
                                // Tuple.Item4 - input = true, output = false
                            var toolParams = res.Parameters;

                            if (toolParams == null)
                            {
                                MessageBox.Show("No Results Found");
                                return;
                            }

                            // string to store dac results json string
                            string dac_results = "";
                            // List to store layer names and paths of output feature classes
                            var outputFeatureClasses = new List<(string Name, string Path)>();
                            // Loop through the parameters
                            foreach (var p in toolParams)
                            {
                                // if it is an output parameter of type DEFeatureClass
                                if (p.Item4 == false && p.Item2 == "DEFeatureClass")
                                {
                                    // add to the outPutFeatureClasses
                                    outputFeatureClasses.Add((p.Item1, p.Item3));
                                }

                                // Set the dac_results string when we find the dac_results parameter
                                if (p.Item1 == "dac_results")
                                {
                                    dac_results = p.Item3;
                                }
                            }
                            
                            // Deserialize JSON dac results to DACData object
                            if (dac_results != "")
                            {
                                // Deserialize the json string into a DACData object
                                DACData dac = JsonConvert.DeserializeObject<DACData>(dac_results);

                                // Update dac data results on Module1
                                Module1.Current.ResultsData = dac;
                            }

                            // Select results
                            var manholeOutput = outputFeatureClasses.Where((l) => l.Name == Module1.ManholeFeatureClassName).FirstOrDefault();
                            await Module1.SelectResultsFeatures(manholeOutput.Path, Module1.ManholeFeatureClassName, Module1.ManholeIdField);

                            var gravPipeOutput = outputFeatureClasses.Where((l) => l.Name == Module1.GravityPipesFeatureClassName).FirstOrDefault();
                            await Module1.SelectResultsFeatures(gravPipeOutput.Path, Module1.GravityPipesFeatureClassName, Module1.GravityPipesIdField);

                            var presPipeOutput = outputFeatureClasses.Where((l) => l.Name == Module1.PressurePipesFeatureClassName).FirstOrDefault();
                            await Module1.SelectResultsFeatures(presPipeOutput.Path, Module1.PressurePipesFeatureClassName, Module1.PressurePipesIdField);

                            // delete output feature classes
                            foreach (var (Name, Path) in outputFeatureClasses)
                            {
                                await Module1.DeleteFeatureClass(Path);
                            }

                            // Call cleanup function
                            Module1.DoPostGpCleanUp();

                            break;
                    }
                }, GPExecuteToolFlags.None);
        }
    }
}
0 Kudos
Flayner
Occasional Contributor

Tks @JamesGough 

0 Kudos