Tasks are bound to online or offline data or services and provide methods to perform asynchronous operations using those resources.
With tasks you can:
- Download, collect, and update geographic information using
GeodatabaseSyncTask
- Download and display tiled basemaps using
ExportTileCacheTask
- Locate addresses on the map and interpolate addresses from map locations using
LocatorTask
- Calculate point-to-point or multi-stop routes and get driving directions using
RouteTask
- Perform complex GIS analysis by executing geoprocessing models using
GeoprocessingTask
Tasks either return their results directly from asynchronous methods on the task, or make use of jobs to provide status updates and results.
Tasks
Some operations return results directly from asynchronous methods on the task. For more complex or longer running operations, tasks make use of jobs instead.
To use tasks that return results directly:
- Create the task by initializing it to use the required data or service.
- Some operations can work both online and offline.
- Define the task inputs.
- Some operations require only simple value inputs (for example a simple geocode operation may only require an address string as input).
- Others require input parameters (for example, to limit a geocode operation to a specific country).
- Call the async operation method, passing in the inputs you defined.
- Use the results from the operation as required, for example to display geocode results on a map.
The code below creates a LocatorTask
using the Esri geocode service.
// Create new locator taks using the supplied Url.
LocatorTask* locatorTask = new LocatorTask(
QUrl("https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer"), this);
When the address string is available, the app calls geocodeWithParametersAsync
to start the task. The code will process the results when the task is complete. The result will be displayed in a Graphic
.
// Handle the geocode with parameter async request on the locator task, using
// the supplied address and geocode parameter inputs.
locatorTask->geocodeWithParametersAsync(address, geocodeParameters)
.then(this, [this, graphic](const QList<GeocodeResult>& geocodeResults)
{
// Continue if there are one or more geocode results.
if (geocodeResults.length() > 0)
{
// Get the first geocode result.
const GeocodeResult geocodeResult = geocodeResults.at(0);
// Get the point location for the geocode result.
const Point point = geocodeResult.displayLocation();
// Set the point geometry on the graphic.
graphic->setGeometry(point);
// Get the attributes for the geocode result.
QMap<QString,QVariant> attributes = geocodeResult.attributes();
// Get the attribute list model for the graphic.
AttributeListModel* attributeListModel = graphic->attributes();
// Set the attributes for the graphic's attribute list model.
attributeListModel->setAttributesMap(attributes);
// Get the envelope extent for the geocode result.
const Envelope envelope = geocodeResult.extent();
// Set the map view's extent to the envelope extent.
m_mapView->setViewpointGeometryAsync(envelope);
}
});
Define input parameters
Tasks offer numerous options that allow you to tailor the operation to your requirements. For example, when geocoding you can restrict your search to a specific area, country, category of place, and/or number of results. When an author publishes a service or packages a resource, they can choose default values for these options that suit the specific data or the most common use case for the service.
To use these default parameter values, tasks provide helper methods that create parameter objects initialized with service-specific values. You can then make any changes to the parameter values before passing them to an operation. Creating these default parameter objects is useful for operations with many options, such as tracing a utility network.
The code below creates a RouteTask
and connects its load
signal. If the route task successfully loads, the RouteTask::createDefaultParametersAsync()
is called which will obtain a Route
. Then RouteTask::solveRouteAsync()
method is called to process the route results.
// Create a new the route task pointing to an online service.
RouteTask* routeTask = new RouteTask(
QUrl("http://sampleserver6.arcgisonline.com/arcgis/rest/services/NetworkAnalysis/SanDiego/NAServer/Route"), this);
// Connect to the route task's load status changed signal.
connect(routeTask, &RouteTask::loadStatusChanged, this, [routeTask, this](LoadStatus loadStatus)
{
// Proceed if the the route task loaded successfully.
if (loadStatus == LoadStatus::Loaded)
{
// Async function to create the default parameters for the route task.
routeTask->createDefaultParametersAsync().then(this, [this, routeTask](RouteParameters routeParameters)
{
// Async function to solve the route task.
routeTask->solveRouteAsync(routeParameters).then(this, [this](RouteResult routeResult)
{
// Get the list of routes from the route result.
const QList<Route> routes = routeResult.routes();
// Get the first route in the list of routes.
const Route route = routes.at(0);
// Get the polyline geometry from the route.
const Polyline polyline = route.routeGeometry();
// Create a new graphic using the polyline geometry.
Graphic* graphic = new Graphic(polyline, this);
// Get the graphics overlay list model from the map view.
GraphicsOverlayListModel* graphicsOverlayListModel = m_mapView->graphicsOverlays();
// Get the first graphics overlay from the graphics overlay list model.
GraphicsOverlay* graphicsOverlay = graphicsOverlayListModel->at(0);
// Get the graphic list model from the graphics overlay.
GraphicListModel* graphicListModel = graphicsOverlay->graphics();
// Add the graphic to the graphic list model.
graphicListModel->append(graphic);
// Get the direction maneuver list model from the route.
DirectionManeuverListModel* directionManeuverListModel = route.directionManeuvers(this);
// Provide feedback on the number of direction maneuvers there are.
qDebug() << directionManeuverListModel->directionManeuvers().count();
// Emit that the route directions have updated.
emit directionsChanged();
// Emit that the route has solved successfully.
emit solveRouteComplete();
});
});
}
});
// Call the route tak's load status function.
routeTask->loadStatus();
Some parameters objects have constructors that you can use if you know the values of all the input parameters you want to use. This can be more efficient when parameter settings are simple.
Work online or offline
Many tasks can work either online by using services, or offline by using local data and resources. For example, you can geocode an address by using the default Esri geocoding service, your own geocoding service, a locator file (.loz
), or a mobile map package (.mmpk
).
Here's the earlier example that creates a LocatorTask
from a URL to a geocoding service.
// Create a new locator task using the provided Url.
LocatorTask* locatorTask = new LocatorTask(
QUrl("https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer"), this);
The following code declares a LocatorTask
from an offline locator stored on the device.
// Create a new locator task using the provided path on the device.
LocatorTask* locatorTask = new LocatorTask(
QDir::homePath()+ "/Locators/SanDiegoStreetAddress/SanDiego_StreetAddress.loc", this);
Another option is to use a locator stored with a mobile map package.
// Get the locator task from the mobile map package (.MMPK).
LocatorTask* locatorTask = mobileMapPackage->locatorTask();
Tasks and jobs
Some tasks expose operations that have multiple stages (like preparing and downloading a geodatabase), and can generate multiple progress messages (such as percentage complete). These types of tasks are always bound to ArcGIS Server (or Local Server for platforms that support it). An example is GeodatabaseSyncTask::generateGeodatabase()
.
Instead of returning results directly, these tasks make use of jobs to monitor status, return progress updates, and return their results. Each Job
represents a specific operation of a task. Jobs are useful for longer-running operations, because they can also be paused, resumed, and canceled. Your app can support a user action or host OS terminating a running job object, and then recreate and resume the job later.
To use operations like these:
- Create the task by initializing it to use the required data or service.
- Define the input parameters for the task.
- Call the async operation method to get a job, passing in the input parameters you defined.
- Start the job.
- Optionally, listen for changes to the job status and check the job messages, for example to update a UI and report progress to the user.
- Listen for the job completion and get the results from the operation. Check for errors in the job, and if successful, use the results.
// Get the export tile cache job from the export tile cache task.
ExportTileCacheJob* exportTileCacheJob = exportTileCacheTask->exportTileCache(defaultExportTileCacheParameters, dataPath);
// Check if there is a valid export tile cache job.
if (exportTileCacheJob)
{
// Connect to the export tile cache job's status changed signal.
connect(exportTileCacheJob, &ExportTileCacheJob::statusChanged, this, [exportTileCacheJob, this]()
{
// Set the correct notifications based on the returned job status.
switch (exportTileCacheJob->jobStatus()) {
case JobStatus::Failed:
emit updateStatus("Export failed");
emit hideWindow(5000, false);
break;
case JobStatus::NotStarted:
emit updateStatus("Job not started");
break;
case JobStatus::Paused:
emit updateStatus("Job paused");
break;
case JobStatus::Started:
emit updateStatus("In progress...");
break;
case JobStatus::Succeeded:
emit updateStatus("Adding TPK...");
emit hideWindow(1500, true);
displayOutputTileCache(exportTileCacheJob->result());
break;
default:
break;
}
});
// Start the export tile cache job.
exportTileCacheJob->start();
}
Calling Job::jobStatus
retrieves the current JobStatus
in the job's workflow. Jobs periodically fire a changed event as they are running, usually with decreasingly frequency as a job progresses. More than one JobMessage
may appear in a change event. The job complete listener is called as soon as the job finishes. Whether successful or not, jobs cannot be restarted.
Report job progress
A job represents an asynchronously running operation that might take some time to finish. As described previously, you can monitor changes to job status for notification when a job has completed, failed, or been canceled, but what about the time in-between? Users may become frustrated waiting for a long job to complete without getting feedback on its progress. Fortunately, jobs provide a mechanism for reporting the current progress (percentage complete) for the running operation they represent.
As the job runs it emits a Job::progressChanged()
signal. You can get the current progress of the job at any point from the Job::progress()
property, an integer representing the percentage of the operation that has been completed. This allows your app to provide more specific information about the status of a running job using UI elements like progress bars, for example.
The following example updates a label in the UI to show the percentage complete for the job.
// Connect to the job's progress changed signal.
connect(downloadJob, &Job::progressChanged, this, [this, downloadJob]()
{
// Update a member variable that is read from QML.
int downloadProgress = downloadJob->progress();
// Display the progress back to the debug window.
qDebug() << "Download progress: " << downloadProgress;
// Emit that the downlaod was successful.
emit downloadProgressChanged();
});
Pause, resume, or cancel a job
Jobs are designed to handle a user exiting an app while the job is running or having the app terminated by the host operating system. Jobs also provide a mechanism for explicitly pausing or canceling the operation.
Cancel a job
Sometimes, the results of a job are no longer required. For example, a user could change their mind about the area of a tile cache they want to download and want to cancel the job and start over.
Calling Job::cancelAsync()
changes JobStatus
to canceling
, cancels the Job
, and waits for any asynchronous, server-side operations to be canceled. After all cancelation tasks complete (including any server-side tasks), JobStatus
changes to failed
and Job::cancelAsync()
returns true. If one or more jobs cannot be canceled, Job::cancelAsync()
returns false.
For example, GenerateOfflineMapJob
is a server-side job that launches several more server-side jobs, depending on the layers in your map. Other examples of server-side jobs include ExportTileCacheJob
, ExportVectorTilesJob
, GenerateGeodatabaseJob
, and GeoprocessingJob
.
You should always cancel unneeded jobs (for example when exiting your app) to avoid placing unnecessary load on the server.
The following example shows code you would add to the Job::statusChanged()
slot to listen for the Job
status. Canceling the job might require additional cleanup of incomplete data or other code on the client.
// Connect to the job's status changed signal.
connect(downloadJob, &Job::statusChanged, this, [](JobStatus status)
{
// Test if the job status is canceling.
if (status == JobStatus::Canceling)
{
// Handle canceling tasks
}
});
Pause and resume a job
Jobs can be long-running operations, so there is no guarantee that they will be completed while the app is running. You can pause a job explicitly using Job::progress()
. For example, when an app is backgrounded and does not have permissions for background operation. Pausing may also be useful if a user wishes to temporarily stop network access for any reason.
Job changed messages will not be received for a paused job. Pausing a job does not stop any server-side processes from executing. While a job is paused, outstanding requests can complete. Therefore, when resuming a job it may have a different state than when it was paused.
You can serialize a job to JSON to persist it if your app is backgrounded or the process is otherwise terminated. When you deserialize it again the JobStatus
will be in the paused state regardless of its state when serialized and should be restarted to resume listening for completion. The job changed listener is a good place to update the job JSON for storage by your app.
The following example shows code to save the state of a job in its Job::statusChanged()
event handler. Additional code could be used to save this text (JSON) as a local file for deserialization later.
// Save the current job to JSON.
const QString jobJson = syncGdbJob->toJson();
The job can be deserialized from a JSON string and resumed if necessary. You must rewire event handlers to monitor the job status (Job::statusChanged()
), for example), as shown in the following example.
// Recreate the job from JSON.
SyncGeodatabaseJob* syncGdbJobFromJson =
static_cast<SyncGeodatabaseJob*>(SyncGeodatabaseJob::fromJson(jobJson, this));
// Connect to the job's status changed signal.
connect(syncGdbJobFromJson, &Job::statusChanged, this, [](JobStatus status)
{
// Handle notifications based on the returned job status.
switch(status){
case JobStatus::Succeeded:
// Handle successful job case
break;
// Handle additional cases...
default:
break;
}
});
// The job will be paused when deserialized from JSON and must be resumed.
syncGdbJobFromJson->start();
Loss of network connection
Additionally, jobs using services are designed to handle situations where network connectivity is temporarily lost without needing to be immediately paused. A started job will ignore errors such as network errors for a period of up to 10 minutes. If errors continue for longer, the job will fail and the message will indicate the loss of network connection.
To handle inconsistent connectivity, you can serialize and pause a job when your app loses connectivity for a few minutes to avoid job failure (as failed jobs cannot be restarted). The job can then be deserialized and resumed when connectivity returns.