Introduction
Quite often we have to deal with slow and lengthy Web Services tasks and consuming such Web Services can be equally frustrating. One solution to overcome this problem is to access the Web Service in an asynchronous manner while running the slow and lengthy Web Service task itself asynchronously. The beauty of the solution is that it is non blocking asynchronous call and the UI remains unaffected.
Using this method also helps you maintain and manage state in your Web Services.
Again this is probably just one possibility to many solutions in dealing with this kind of an issue.
Background
I came across this issue while surfing the ASP.NET forum. I found it to be an extremely interesting problem simply because the solution could be as convoluted as this.
When the client that is consuming the slow and lengthy service initiates the Web Service call, the webmethod generates and returns a GUID. Using this as a key we create an entry in the HTTPRuntime
Cache and spawn a background thread that runs the slow and lengthy task and when complete, will update the HttpRuntime
Cache entry with the result.
The client periodically polls the Web Service to check if the slow and lengthy task has completed.
Using the Code
The download zip includes a Web app client that incorporates an AJAX based polling system and a Web Service that runs the task in a background thread.
Client Code
The client initiates the web request by calling the startCalculate
web method and storing the id
it receives in a session object called key:
protected void Button1_Click(object sender, EventArgs e)
{
Session["key"] = sv.startCalculate();
Lab_Result.Text = "Retrieving the result for ID " + Session["key"].ToString()
+ " ...";
}
The client uses the AJAX timer control's Tick
event to do the polling. It calls the retrieveValue(string ID)
by passing the ID
stored in the session object called key
. If returned value is "Waiting
" or contains "Retrieving
" I continue polling for the result. Once the result is returned, it will no longer poll reducing on unnecessary calls to the Web Service.
protected void Timer1_Tick(object sender, EventArgs e)
{
if (Session["key"] != null)
{
TmpVar = Lab_Result.Text;
if ((TmpVar == "Waiting!") || (TmpVar.Contains("Retrieving")) ||
(TmpVar == null))
{
TmpVar = sv.retrieveValue(Session["key"].ToString());
}
Lab_Result.Text = TmpVar;
}
}
Web Service Code
The following Webservice SlowServiceInMemory
exposes two web methods startCalculate()
and retrieveValue(string ID)
.
The startCalculate()
method generates an ID, which it returns to the consuming client. It creates a HTTPRuntime
Cache entry using the ID and calls the calculate(string key)
method that in turn spawns a background thread to complete the slow and lengthy task.
[WebMethod]
public string startCalculate()
{
string key = Guid.NewGuid().ToString();
insertCacheObject(key, "Waiting!");
calculate(key);
return key;
}
The insertCacheObject
method makes the Cache entry using the string
key as an identifier.
private void insertCacheObject(string key, string ver)
{
HttpRuntime.Cache.Insert(
key
, (object)ver
, null
, DateTime.Now.AddSeconds(15)
, Cache.NoSlidingExpiration);
}
The retrieveValue(string key)
web method retrieves the result that is stored in the HttpRuntime.Cache
entry using the key.
[WebMethod]
public string retrieveValue(string key)
{
string tmp = "Waiting!";
try
{
tmp = HttpRuntime.Cache.Get(key).ToString();
}
catch (Exception ex)
{
tmp = "Error " + ex.Message;
}
return tmp;
}
The calculate(string key)
method spawns a background thread to perform the slow and lengthy task. It then stores the result in the Cache entry. You can further experiment by changing the Thread.Sleep
value to a smaller or larger number.
private void calculate(string key)
{
Thread th = new Thread(
delegate()
{
Thread.Sleep(10000);
string Tmp = "Result for long running class!";
insertCacheObject(key, Tmp);
});
th.IsBackground = true;
th.Start();
}
Points of Interest
You could extend this implementation to persist the result to a database or a flat file instead of the cache.
History
- 27th April, 2009: Initial post
I would like to implement asynchronous calls to the Web Service using the IAsynchResult
pattern. The IAsynchResult
pattern supports a non blocking callback implementation for asynchronous calls to the Web Service.