Skip To Content

Asynchronous programming

In this topic

Synchronous code execution proceeds in a linear path to process a queue of instructions in order. In this mode of execution, a particular set of code in the queue cannot be executed until all instructions ahead of it have been completed. This works fine for code that completes quickly, but it can create a bottleneck if working with processes that take longer to execute. Such bottlenecks cause frustration for users of your app, as they make the UI unresponsive until the blocking code completes.

By contrast, code executed asynchronously does not proceed in a linear fashion. Instead, code is executed on an available thread in the thread pool and results (if any) are returned upon completion. This allows you to off-load execution of longer-running processes, freeing up the UI thread so the app remains responsive to user interaction.

The .NET Framework 4.5 provides a simplified asynchronous approach that allows you to write your code in the familiar logical and linear structure that you use for synchronous code. The .NET APIs use asynchronous execution in areas where performance may be an issue, such as web access, working with files, and media capture.

ArcGIS Runtime SDK for .NET also leverages the .NET Framework 4.5 asynchronous pattern to provide asynchronous methods throughout the API. Although calling an asynchronous method requires slightly different syntax than a synchronous call, the new pattern hides much of the complexity involved and allows your code to be written with a much more linear structure compared to previous asynchronous approaches.

Task-based asynchronous pattern

In the .NET task-based asynchronous pattern (TAP), asynchronous functions are represented by System.Threading.Tasks.Task and System.Threading.Tasks.Task<TResult> types. Task<TResult> represents operations that return a value of type TResult and Task is for functions that don't return a value. The async keyword is used to mark a function as asynchronous, which must return one of those two types.

The following example shows the signature for an asynchronous function. It is marked with the async keyword and has a return type of Task<string>. By convention, asynchronous functions are named with the suffix Async.

private async Task<string> GetSiteContentAsync()
{


}

As mentioned previously, calls made to an asynchronous function will return Task or Task<TResult> objects, which represent ongoing work. The task contains information about the state of the asynchronous process and, when it completes, either the final result from the process or the exception thrown when the process failed.

To get the actual value returned from the asynchronous operation, you need to await completion of the task using the await keyword. When execution hits a line with await, control is returned to the calling function until the task completes.

The following example shows a call to an asynchronous method (System.Net.Http.HttpClient.GetStringAsync) to get a Task<string>. It then uses await to get the string result of the task operation.

private async Task<string> GetSiteContentAsync()
{
    // use an HttpClient to get the contents of a web page
    var client = new System.Net.Http.HttpClient();


    // call the async method GetStringAsync to return a Task<string>
    // the task will asynchronously read the contents of the specified web site
    Task<string> getContentsTask = client.GetStringAsync("http://www.esri.com");


    // await the task to get the result 
    // note: control is returned to the calling code at this point (until the task completes)
    string urlContents = await getContentsTask;


    // code following "await" does not execute until the task completes
    return urlContents;
}
Note:

Functions that use the await keyword must be marked with the async keyword in their definition.

Although it should be avoided if possible, an async function can be defined with a void return type. A common scenario is when defining an async event handler, whose signature must retain the void return type.

Caution:

An async function with a void return type cannot be awaited, and the caller won't be able to catch any exceptions thrown by the function.

For more information about asynchronous programming in .NET, see the MSDN article Asynchronous Programming with Async and Await.

Async methods in ArcGIS Runtime

ArcGIS Runtime SDK for .NET leverages the task-based asynchronous pattern used by the .NET Framework. Throughout the API, you'll find several asynchronous methods, all of which are named using the Async suffix. Many of the ArcGIS Runtime methods wrap calls to REST services, and since they require web communication, it makes sense to make such calls asynchronously. This ensures that your app stays responsive while a response from the server is awaited.

The following example illustrates the use of an Esri.ArcGISRuntime.Tasks.Query.FindTask to asynchronously search for features containing the specified text in the name.

private async Task<int> GetFeatureCountAsync()
{
    // create a Uri that points to the USA map service
    var uri = new System.Uri("http://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer");


    // create a new FindTask
    var findTask = new Esri.ArcGISRuntime.Tasks.Query.FindTask(uri);


    // create a new FindParameters
    var findParams = new Esri.ArcGISRuntime.Tasks.Query.FindParameters();


    // define the term to search for
    findParams.SearchText = "Virginia";


    // define the layers to search (cities)
    var layerIds = new List<int>();
    layerIds.Add(2);
    findParams.LayerIDs = layerIds;


    // execute asynchronously, store the resulting Task<FindResult>
    var resultTask = findTask.ExecuteAsync(findParams);


    // await the (FindResult) return value
    var findResult = await resultTask;


    // return the count of features found
    return findResult.Results.Count;
}
See Search for features for more examples of using asynchronous calls to search for features.

Sometimes, it's not the result of an asynchronous method that's important, but waiting for a particular task to complete. For example, you may have code that depends on the loading of all layers in the map. You can await the MapView.LayersLoadedAsync method to ensure loading of all layers is complete, as shown in the following example.

private async Task AddGraphicsAsync()
{
    // wait for all layers to load
    await MyMapView.LayersLoadedAsync();


    // you can now work with the graphics layer
    var graphicsLyr = MyMapView.Map.Layers["MyGraphicsLayer"] as GraphicsLayer;


    // ... code here to add graphics ...
}

Asynchronous tasks generally run on a background thread for your application, which may have ramifications for working with objects in the UI. See Threading considerations for more information.

Handle exceptions from asynchronous calls

If an error is encountered while executing an asynchronous call, System.Threading.Tasks.Task.Exception will be set. You can trap for such exceptions by wrapping the await call in a try catch block such as the one shown in the following code example. If the exception is raised from the underlying REST call, you can expect an Esri.ArcGISRuntime.Http.ArcGISWebException to be thrown.

QueryResult queryResult = null;
Task<QueryResult> task = queryTask.ExecuteAsync(queryParams);
try
{
    queryResult = await task;
}
catch (Esri.ArcGISRuntime.Http.ArcGISWebException exp)
{
    // "Invalid URL", "Invalid parameter", e.g.
    // ... handle exception here ...
}
catch (Exception exp)
{
    // ... handle exception here ...
}


if (queryResult != null)
{
    // Process results ...
}

Cancel execution

A task can be canceled during the course of its asynchronous execution by using a System.Threading.CancellationToken, which can optionally be passed into most ExecuteAsync methods for a task. A task can be canceled manually (for example, if the user clicks a Cancel button) or by specifying a maximum time interval that the task is allowed to run. The following code example incorporates both techniques by setting a time limit (10 seconds) after which the task will be canceled and also calls the Cancel method if the user clicks the Cancel button.

private System.Threading.CancellationTokenSource canceller;
private async void DoQueryAsync()
{
    var url =
       "http://sampleserver5.arcgisonline.com/arcgis/rest/services/RedlandsEmergencyVehicles/FeatureServer/1";
    var queryTask = new QueryTask(new Uri(url));


    // Create a new Query object to define parameters (get all features)
    var queryParams = new Esri.ArcGISRuntime.Tasks.Query.Query("1=1");


    // Set up the cancellation token
    this.canceller = new System.Threading.CancellationTokenSource();
    this.canceller.CancelAfter(10000); // 10 seconds
    var token = this.canceller.Token;


    // Execute the task with the cancellation token; await the result
    QueryResult queryResult = null;
    Task<QueryResult> task = queryTask.ExecuteAsync(queryParams, token);
    try
    {
        queryResult = await task;
    }
    catch (System.Threading.Tasks.TaskCanceledException exp)
    {
        // ... handle cancel here ...
    }


    if (queryResult == null) { return; }


    // Get the list of features from the result
    var resultFeatures = queryResult.FeatureSet.Features;


    // Create a collection of Graphics from the features
    var src = from f in resultFeatures select new Graphic(f.Geometry);


    // Get the graphics layer in the map; set the source with the graphics created from the results
    var graphicsLayer = Map.Layers["Graphics"] as GraphicsLayer;
    graphicsLayer.GraphicsSource = src;
}
 

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
    // If the Cancel button is clicked, cancel the task using the CancellationTokenSource object
    this.canceller.Cancel();
}

Canceling the task will cause a System.Threading.Tasks.TaskCanceledException to be thrown. When allowing the user to cancel a task, you'll need to anticipate and handle these exceptions in your code.

Related topics