views:

598

answers:

2

We have a C# WebMethod that is called synchronously by a Delphi CGI (don't ask!). This works fine except when we switch to our disaster recovery environment, which runs a lot slower. The problem is that the Delphi WinInet web request has a timeout of 30 seconds, which cannot be altered due a Microsoft-acknowledged bug. In the disaster recovery environment, the C# WebMethod can take longer than 30 seconds, and the Delphi CGI falls flat on its face.

We have now coded the C# WebMethod to recognise the environment it is in, and if it is in disaster recovery mode then we call the subsequent method in a thread and immediately respond to the CGI so that it is well within the 30 seconds. This makes sense in theory, but we are finding that these threaded calls are erratic and are not executing 100% of the time. We get about a 70% success rate.

This is clearly unacceptable and we have to get it to 100%. The threads are being called with Delegate.BeginInvoke(), which we have used successfully in other contexts, but they don't like this for some reason.... there is obviously no EndInvoke(), because we need to respond immediately to the CGI and that's the end of the WebMethod.

Here is a simplified version of the WebMethod:

[WebMethod]
public string NewBusiness(string myParam)
{
    if (InDisasterMode())
    {
        // Thread the standard method call
        MethodDelegate myMethodDelegate = new MethodDelegate(ProcessNewBusiness);
        myMethodDelegate.BeginInvoke(myParam, null, null);
        // Return 'ok' to caller immediately
        return 'ok';
    }
    else
    {
        // Call standard method synchronously to get result
        return ProcessNewBusiness(myParam);
    }
}

Is there some reason that this kind of 'fire and forget' call would fail if being used in a WebService WebMethod environment? If so then is there an alternative?

Unfortunately altering the Delphi side is not an option for us - the solution must be in the C# side.

Any help you could provide would be much appreciated.

A: 

As the documentation says, EndInvoke should be always called, so you have to create a helper for doing FireAndForget operations like this one: http://www.reflectionit.nl/Blog/default.aspx?guid=ec2011f9-7e8a-4d7d-8507-84837480092f

I paste the code:

public class AsyncHelper {
 delegate void DynamicInvokeShimProc(Delegate d, object[] args); 

 static DynamicInvokeShimProc dynamicInvokeShim = new 
   DynamicInvokeShimProc(DynamicInvokeShim); 

 static AsyncCallback dynamicInvokeDone = new 
   AsyncCallback(DynamicInvokeDone); 

  public static void FireAndForget(Delegate d, params object[] args) { 
    dynamicInvokeShim.BeginInvoke(d, args, dynamicInvokeDone, null); 
  } 

  static void DynamicInvokeShim(Delegate d, object[] args) { 
   DynamicInvoke(args); 
  } 

  static void DynamicInvokeDone(IAsyncResult ar) { 
    dynamicInvokeShim.EndInvoke(ar); 
 } 
}

We use this code successfully in our application, although it is not web.

jmservera
You don't really need to build your own "AsyncHelper" class... just use ThreadPool.QueueUserWorkItem
Timothy Khouri
Thanks for the suggestion jmservera - much appreciated, but I found the use of ThreadPool a much simpler solution.
JamesW
You're welcome. The Timothy Khouri's solution is also faster.
jmservera
+6  A: 

Do you try to use the "HttpContext" in your method? If so, you should store it in a local variable first... also, I'd just use ThreadPool.QueueUserWorkItem.

Example:

[WebMethod]
public string NewBusiness(string myParam)
{
    if (InDisasterMode())
    {
        // Only if you actually need this...
        HttpContext context = HttpContext.Current;

        // Thread the standard method call
        ThreadPool.QueueUserWorkItem(delegate
        {
            HttpContext.Current = context;

            ProcessNewBusiness(myParam);
        });

        return 'ok';
    }
    else
    {
        // Call standard method synchronously to get result
        return ProcessNewBusiness(myParam);
    }
}
Timothy Khouri
I love it when there's a simple answer. This seems to work perfectly on our test server 100% of the time, even without the HttpContext code. The disaster environment test itself will have to wait for a suitable window, but I'm confident we've nailed it. Thanks for yor help!
JamesW
The test on the Disaster Recovery server was a complete success. Thanks again!
JamesW
I am finding it hard to convert this method into VB.NET (clients preference) for .NET 2.0, can you help.
Binoj Antony