views:

387

answers:

5

As part of a large automation process, we are calling a third-party API that does some work calling services on another machine. We discovered recently that every so often when the other machine is unavailable, the API call will spin away sometimes up to 40 minutes while attempting to connect to the remote server.

The API we're using doesn't offer a way to specify a timeout and we don't want our program waiting around for that long, so I thought threads would be a nice way to enforce the timeout. The resulting code looks something like:

 Thread _thread = new Thread(_caller.CallServices());

 _thread.Start();
 _thread.Join(timeout);

 if (_thread.IsAlive)
 {
      _thread.Abort();
      throw new Exception("Timed-out attempting to connect.");
 }

Basically, I want to let APICall() run, but if it is still going after timeout has elapsed, assume it is going to fail, kill it and move on.

Since I'm new to threading in C# and on the .net runtime I thought I'd ask two related questions:

Is there a better/more appropriate mechanism in the .net libraries for what I'm trying to do, and have I committed any threading gotchas in that bit of code?

+2  A: 

Bad idea. Thread.Abort doesn't necessarily clean up the mess left by such an interrupted API call.

If the call is expensive, consider writing a separate .exe that makes the call, and pass the arguments to/from it using the command line or temporary files. You can kill an .exe much more safely than killing a thread.

Daniel Earwicker
+5  A: 

Thread.Abort() is a request for the thread to abort, and gives no guarantee that it will do so in a timely manner. It is also considered bad practice (it will throw a thread abort exception in the aborted thread, but it seems like the 3rd party API offers you no other choices.

If you know (programmatically) the address of the remote service host you should ping it before you transfer control to the 3rd party API.

If not using a backgroundworker, you could set the thread's IsBackgroundThread to true, so it doesn't keep your program from terminating.

Cecil Has a Name
A: 

It might work, but nobody could say for sure without an understanding of the third-party API. Aborting the thread like that could leave the component in some invalid state that it might not be able to recover from, or maybe it won't free resources that it allocated (think - what if one of your routines just stopped executing half-way through. Could you make any guarantees about the state your program would be in?).

As Cicil suggested, it might be a good idea to ping the server first.

Cybis
+2  A: 

You can also just use a delegate... Create a delegate for the method that does the work, Then call BeginInvoke on the delegate, passing it the arguments, and a callback function to handle the return values (if you want)... Immediately after the BeginInvoke you can wait a designated time for the asynch delegate to finish, and if it does not in that specified time, move on...

   public delegate [ReturnType] CallerServiceDelegate
          ([parameter list for_caller.CallService]);

   CallerServiceDelegate callSvcDel = _caller.CallService;
   DateTime cutoffDate = DateTime.Now.AddSeconds(timeoutSeconds);
   IAsyncResult aR = callSvcDel.BeginInvoke([here put parameters], 
                                             AsynchCallback, null); 
   while (!aR.IsCompleted && DateTime.Now < cutoffDate)
       Thread.Sleep(500);
   if (aR.IsCompleted)
   {
      ReturnType returnValue = callSvcDel.EndInvoke(aR);
      // whatever else you need to do to handle success
   }
   else
   {
      callSvcDel.EndInvoke(aR);
      // whatever you need to do to handle timeout
   }

NOTE: as written AsynchCallback could be null, as the code retrieves the return value from the EndInvoke(), but if you want to you can have the CallService() method call the AsynchCallback delegate and pass it the return values instaed...

Charles Bretana
How do you handle the timeout though? Do you just let the component keep trying, for up to 40 minutes, to connect to the missing server? I think the question was about how to make the component stop trying to connect.
Cybis
Sorry, was editing to add sample code... the timeout question is addressed by the DateTime.Now < cutoffDate in the while condition
Charles Bretana
A: 

Does your application run for long periods of time or is it more of a run-as-needed application? If it's the latter, I personally would consider using the Thread.Abort() option. While it may not be the most desirable from a purist's perspective (resource management, etc.), it is certainly straightforward to implement and may foot the bill given the way your particular application works.

The idea of a separate executable makes sense. Perhaps another option would be to use AppDomains. I'm not an expert in this area (I welcome refinements/corrections to this), but as I understand it, you'd put the API call in a separate DLL and load it into a separate AppDomain. When the API call is finished or you have to abort it, you can unload the AppDomain along with the DLL. This may have the added benefit of cleaning up resources that a straightforward Thread.Abort() will not.

Matt Davis