How to Initiate Long Running Tasks in IVR Systems

When Twilio makes a request to your application, we expect it to return a response in no more than 15 seconds. But there are plenty of scenarios where your app might need a bit more time than that. Scenarios like running complex queries, or calls to external services can cause your app to miss the 15 second request timeout, which will result in an error.

In this blog post I’ll show you how you can use the Task Parallel Libraries introduced in .NET 4.0 to design IVR systems that initiate a long running task as part of a call flow.

The Package Search Scenario

In order to show how to handle long running tasks in an IVR, I’ll use the scenario of a package status lookup system where the system needs to make a request to an external service in order to retrieve the package status while the call is in progress.
The diagram below shows the high level call flow of my IVR call flow:

The flow itself is pretty basic. The IVR presents the user with a greeting and then prompts them to enter a package ID. Once the application receives the package ID, it asks the caller to wait while it tries to retrieve the package status. The caller is put into an audio loop while the system performs the search for the package. Finally, once the search is completed the call is redirected to let the user know the results of the search.

To create the system, I’ve set up a simple ASP.NET MVC application and added a controller class named CallController which is where the logic of the IVR will live. As the user interacts with the IVR, Twilio will make requests to different action methods in the CallController, which will return TwiML verbs telling Twilio how to handle the call.

The part of the system that I’ll be focusing on in this post is initiating the package search as an asynchronous task, placing the call into the audio loop during the search, and then redirecting it out of the loop once the search completes.

Simulating a Package Lookup Service

In the call flow, once the application receives the package ID, it needs to initiate a search using an external service. In order to simulate this service in my sample, I’ve set up a class named PackageService which exposes a single method named LocatePackage.
The package service simulates three scenarios:

  • Searching for and locating a Package with the provided ID
  • Searching for and failing to find a Package with the provided ID
  • An exception occurring during the execution of the search

Each of these scenarios is also encumbered by a 10 second delay in execution which I create by calling Thread.Sleep.

Task Parallel Library

Because of the delay I’m simulating in the PackageService, if I try to execute the package search in my action method by calling the LocatePackage method directly, the request by Twilio will time out and the phone call will fail.

To work around this delay, I need to find a way to execute the search asynchronously in order to let the action method continue to execute so it can return the TwiML before the request times out. To do this I’m going to leverage the Task Parallel Library (TPL), which is included in .NET 4.0.

The TPL is a set of public types and APIs in the System.Threading and System.Threading.Tasks namespaces that make it easy for developers to add parallelism and concurrency to their applications.

To execute the LocatePackage method asynchronously using the TPL, I created a new Task<TResult> using the StartNew method. This method lets me pass in the method instance I want it to execute.

Now when the action method is requested by Twilio, the LocatePackage method is run asynchronously letting the action method continue to execute and return TwiML within the request timeout. But what TwiML should it return?

While the search is happening, I want to have the caller hear an audio track that lets them know the IVR is still working. By using the Play verb and its loop attribute I can create the audio equivalent of the hourglass icon in Windows, or the beachball icon on a Mac. The code below shows the TwiML the action method returns:

The loop attribute is given value of 0 which tells Twilio to loop the audio file forever. This is important because I’ve chosen to use a fairly small mp3 file in order to minimize the time it takes Twilio to download it from my server and transcode it, and because the amount of time that it will take to perform the lookup is variable.

So at this point if I deploy and test the IVR system, I’ll be able to enter a package ID to initiate the package search and the system will place my call into the audio loop. But how does the system know when the search has finished, and when it has finished how does it get the caller out of the audio loop?

To signal that the Task has completed, I’m going to use the Task class’ ContinueWith method. This method creates a continuation that will execute once the Task completes, and in that method I can use the Twilio REST API to redirect the call out of the audio loop:

You can see that when I call the RedirectCall method which is part of the Twilio .NET helper library, I pass in the CallSid of the call I want to modify, and the URL I want the Twilio to request to get the new call instructions.

There is one problem I have to solve however in order to use the RedirectCall method in the continuation. By the time the continuation executes the action method has already returned, therefore I don’t have access to the CallSid that is passed into the action method, and using the MVC UrlHelper to generate a URL will fail since I have no HttpContext.

To work around this, I can use a different overload of the StartNow method to pass in some object state in the form of a Dictionary:

Once the search completes and the continuation executes, I can grab the CallSid and URL from the dictionary and call the RedirectCall method. Also, because the Task returns the result of the PackageLookup method into the continuation, I can pass package status into the redirect URL so that I can inform the caller of the status:

Now if I deploy and test the IVR, once the package search completes the call redirected to a new URL that executes the Complete action method, and I should hear the status of my package.

Because I’m using a dictionary, I can pass other URLs into the continuation so that I can direct the call to specific flows in cases where the package cannot be located, or an exception occurs during the service call.

Summary

As you can see, the Task Parallel Library makes it really easy to create IVR systems that include long running tasks, and by using some TwiML and the Twilio REST API you can create a good experience for the users of the IVR while those tasks execute.

You can download all of the source code for this sample from GitHub. As always, I’d love to hear what you think of this post. If you have any questions or comments, you can tweet me at @devinrader, or shoot an email to devin@twilio.com.