views:

839

answers:

4

On a web page, I am calling a third party who does not allow me to set timeout programatically. I call BeginInvoke and use the AsyncWaitHandle.WaitOne to wait a specified amount of time.

If the call times out, I move on and forget about the thread call I began. My question is, do I still have to call EndInvoke somehow in a timeout situation? The "CAUTION" remark on this MSDN page makes me wonder if I should: http://msdn.microsoft.com/en-us/library/2e08f6yc(VS.71).aspx

If you believe I should, then the next question is if my web page is done processing and returns to the client before the third party comes back, would the callback method even be there listening to run the code? Doesn't the server stop looking for activitiy once my request/response is done?

Here's the code I'm using:

public class RemotePaymentProcessor
{
    private delegate string SendProcessPaymentDelegate(string creditCardNumber);

    private string SendProcessPayment(string creditCardNumber)
    {
        string response = string.Empty;
        // call web service
        SlowResponseService.SlowResponseService srs = new WebServiceTimeout.SlowResponseService.SlowResponseService();
        response = srs.GetSlowResponse(creditCardNumber);
        return response;
    }

    public string ProcessPayment(string creditCardNumber, int timeoutMilliseconds)
    {
        string response = string.Empty;

        SendProcessPaymentDelegate sppd = new SendProcessPaymentDelegate(SendProcessPayment);
        IAsyncResult ar = sppd.BeginInvoke(creditCardNumber, null, new object());
        if (!ar.AsyncWaitHandle.WaitOne(timeoutMilliseconds, false))
        {
            // Async call did not return before timeout
            response = "TIMEOUT";
        }
        else
        {
            // Async call has returned - get response
            response = sppd.EndInvoke(ar);
        }
        return response;
    }
}
A: 

Update:
Seems like you need to call EndInvoke always for an async call (unless its Control.BeginInvoke) or risk leaking resources.

Here's a discussion that is on the same lines. The solution suggested is to spawn a thread that will wait for the delegate to actually complete and call EndInvoke. However in case of a really Looong timeout I think that thread would just hang.

Its an edge case that doesn't seem to be documented that well... maybe coz timeouts aren't supposed to happen.. exceptional cases

Gishu
+1  A: 

It's possible that if you don't call EndInvoke, you'll leak some resource (allocated by BeginInvoke).

So to be totally safe, always call EndInvoke() (which will block, so do it on some background thread you don't need, or switch to passing a callback so as not to burn a thread while waiting).

In practice I don't know how often it matters (I think many AsyncResults will not leak, so you may get lucky and be safe in this instance).

All of this has almost nothing to do with request-response and web servers and whatnot, this is just how the Begin/End programming model works, regardless of what application you're using it in.

Brian
In regard to request-response: if I pass a callback method, but in the mean time complete my buttonclick function and return to client, is there anyone listening when that BeginInvoke thread returns? In a local app, if Main() got done and closed the app, the callback never got fired.
Chad
A: 

For the general case of the .NET async pattern, calling EndXXX when you don't want to complete the operation started with BeingXXX will be a mistake because if EndXXX is called before the operation has completed it should block until it completes. Which isn't much help for a timeout.

A specific API might be different (e.g. WinForms explicitly doesn't require EndInvoke).

See §9.2 of "Framework Design Guidelines" (2nd ed). Or msdn.

Richard
A: 

Well, I couldn't ignore the advice I saw everywhere that if I did not make sure to call EndInvoke, I MIGHT leak some resources, so I had to try to get that in to sleep at night and not worry I was approaching a cliff.

The solution I found used an async callback function. If the call returned in time, I call EndInvoke there. If not, I continue my button click, and let the async callback function clean up the mess with EndInvoke.

To answer my own question about a web app and "will anyone be there to listen after I time out and move on", I found that they will - even if I timed out and moved on, if I watched the later output, that async call will return and run the call back function, even if I've already returned output to the client.

I used some of what I found at: http://www.eggheadcafe.com/tutorials/aspnet/847c94bf-4b8d-4a66-9ae5-5b61f049019f/basics-make-any-method-c.aspx

...as well as combining with the callback stuff I found elsewhere. Here's a little sample function of what I did below. It combines some of what I found at Thanks for everyone's input!:

public class RemotePaymentProcessor

{
string currentResponse = string.Empty;

private delegate string SendProcessPaymentDelegate(string creditCardNumber);    
private string SendProcessPayment(string creditCardNumber)    
{        
 SlowResponseService.SlowResponseService srs = new WebServiceTimeout.SlowResponseService.SlowResponseService();        
 string response = srs.GetSlowResponse(creditCardNumber);        
 return response;    
}    

public string ProcessPayment(string creditCardNumber, int timeoutMilliseconds)    
{        
 string response = string.Empty;        
 SendProcessPaymentDelegate sppd = new SendProcessPaymentDelegate(SendProcessPayment);        
 IAsyncResult ar = sppd.BeginInvoke(creditCardNumber,  new AsyncCallback(TransactionReturned), sppd);        
 if (!ar.AsyncWaitHandle.WaitOne(timeoutMilliseconds, false))        
 {            
  // Async call did not return before timeout            
  response = "TIMEOUT";        
 }        
 else         
 {            
  // Async call has returned - get response            
  response = sppd.EndInvoke(ar);        
 }        

 currentResponse = response; // Set class variable
 return response;    
}

private void TransactionReturned(IAsyncResult ar)
{
 string response = string.Empty;

 // Get delegate back out of Async object
 SendProcessPaymentDelegate sppd = (SendProcessPaymentDelegate)ar.AsyncState;

 // Check outer class response status to see if call has already timed out
 if(currentResponse.ToUpper().Equals("TIMEOUT"))
 {
  // EndInvoke has not yet been called so call it here, but do nothing with result
  response = sppd.EndInvoke(ar);
 }
 else
 {
  // Transaction must have returned on time and EndInvoke has already been called.  Do nothing.
 }  

}

}

Chad