Today I've searched some time to a specific case we have for calling an (external) ASP.NET web service with the following requirements:
- Calls must be 开发者_JS百科done asynchronous
- A timeout must implemented, because web service can take long time to execute
On the internet and StackOverflow many questions appear on this subject, but are either dated or are suggesting using the WebRequest.TimeOut
property which is only applicable for synchronous calls.
One alternative is using an System.Threading.Timer
. Starting the timer just before starting the call and cancelling it when it reaches the TimerCallback
.
However, I think there should be a more common approach to such cases. Unfortunately couldn't find it so far. Anyone has an idea for setting client side timeouts on async web service calls?
Thanks in advance.
Please check your app.config it will have some settings for servicemodel and it has various values that can be configured.
When I added new Service Reference, I can see following things in my app.config,
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="HeaderedServiceSoap"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00"
sendTimeout="00:01:00"
allowCookies="false"
bypassProxyOnLocal="false"
hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536"
maxBufferPoolSize="524288"
maxReceivedMessageSize="65536"
messageEncoding="Text"
textEncoding="utf-8"
transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32"
maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096"
maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None"
proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName"
algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint
address="http://localhost/MyService.asmx"
binding="basicHttpBinding"
bindingConfiguration="HeaderedServiceSoap"
contract="WSTest.HeaderedServiceSoap"
name="HeaderedServiceSoap" />
</client>
</system.serviceModel>
Try removing and adding reference again, make sure your app's target framework is 4.0 and you are adding Service Reference (Not the Web Reference).
Indeed you cannot always use WebRequest.TimeOut
for async operations; at least not for all implementers of the abstract WebRequest
class. For instance it is documented on msdn that this property is ignored when calling HttpWebRequest.BeginGetResponse
to start an async operation. It is explicitly stated that the TimeOut
property is ignored and that it's the user's responsibility to implement timeout behavior if required.
In the example code coming with the HttpWebRequest.BeginGetResponse
documentation on msdn, a ManualResestEvent allDone
in combination with a WaitOrTimerCallback
is used as follows:
IAsyncResult result = (IAsyncResult) myHttpWebRequest.BeginGetResponse(
new AsyncCallback(RespCallback), myRequestState);
// TimeoutCallback aborts the request if the timer fires.
ThreadPool.RegisterWaitForSingleObject (result.AsyncWaitHandle,
new WaitOrTimerCallback(TimeoutCallback),
myHttpWebRequest,
DefaultTimeout,
true);
// The response came in the allowed time. The work processing will happen in the
// callback function RespCallback.
allDone.WaitOne();
Please see the complete example on msdn.
The bottom line is you have to implement this yourself.
i worked up a little project that demonstrates how to do this; it was not as simple as i figured it would be, but, then, what ever is?
here's the whole project with a webservice and also a client in WPF that has a buttons for calling both with and without timeout http://www.mediafire.com/file/3xj4o16hgzm139a/ASPWebserviceAsyncTimeouts.zip. i'll put some relevant snippets below. i used the DispatcherTimer class as described in the code for doing the timeouts; it looks like this object is apparently WPF friendly and (should) relieve most if not all of the synchronization issues that could otherwise be encountered.
NOTE: it probably can be done, somehow, with WCF-style "Service References", however, i was not able to figure out the way and hit a lot of dead ends. i finally ended up going with an older style "Web Reference" (which you can get to by going to "Add Service Reference...", choosing the "Advanced" button, and then choosing "Add Web Reference".
the reason i went with the helper class was to demonstrate what can be done in case you have many calls; keeping track of everything would get messy very quickly if we didn't have that. also, it would probably be possible to get a more generic version where almost all the handling could be done in the code, but the WCF code which took most of my time did not lend itself to that kind of handling due to the way generics are used in the service reference code. i have only quickly looked at the web service code, and it looks more likely that it could be done, but i unfortunately didn't have enough time to play around with that part of things. let me know if you would like me to take a further look and i'll see what i can do.
now on with the show! ;)
helper for doing callback: AsyncCallHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// contains base classes for webservice calls
using System.ServiceModel;
// contains the DispatcherTimer class for callback timers
using System.Windows.Threading;
namespace ASPSandcastleWPFClient
{
/// <summary>
/// DispatcherTimer usage info thanks to:
///
/// Wildermuth, Shawn, "Build More Responsive Apps With The Dispatcher", MSDN Magazine, October 2007
/// Original URL: http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
/// Archived at http://www.webcitation.org/605qBiUEC on July 11, 2011.
///
/// this class is not set up to handle multiple outstanding calls on the same async call;
/// if you wish to do that, there would need to be some sort of handling for multiple
/// outstanding calls designed into the helper.
/// </summary>
public class AsyncCallHelper
{
#region Static Defaults
private static TimeSpan myDefaultTimeout;
/// <summary>
/// default timeout for all instances of the helper; should different timeouts
/// be required, a member should be created that can override this setting.
///
/// if this is set to null or a value less than zero, the timout will be set
/// to TimeSpan.Zero, and the helper will not provide timeout services to the
/// async call.
/// </summary>
public static TimeSpan DefaultTimeout
{
get
{
return myDefaultTimeout;
}
set
{
if ((value == null) || (value < TimeSpan.Zero))
myDefaultTimeout = TimeSpan.Zero;
else
myDefaultTimeout = value;
}
}
#endregion
/// <summary>
/// creates an instance of the helper to assist in timing out on an async call
/// </summary>
/// <param name="AsyncCall">the call which is represented by this instance. may not be null.</param>
/// <param name="FailureAction">an action to take, if any, upon the failure of the call. may be null.</param>
public AsyncCallHelper(Action AsyncCall, Action FailureAction)
{
myAsyncCall = AsyncCall;
myFailureAction = FailureAction;
myTimer = new DispatcherTimer();
myTimer.Interval = DefaultTimeout;
myTimer.Tick += new EventHandler(myTimer_Tick);
}
/// <summary>
/// Make the call
/// </summary>
public void BeginAsyncCall()
{
myAsyncCall();
if (DefaultTimeout > TimeSpan.Zero)
{
myTimer.Interval = DefaultTimeout;
myTimer.Start();
}
}
/// <summary>
/// The client should call this upon receiving a response from the
/// async call. According to the reference given above, it seems that
/// the WPF will only be calling this on the same thread as the UI,
/// so there should be no real synchronization issues here.
///
/// In a true multi-threading situation, it would be necessary to use
/// some sort of thread synchronization, such as lock() statements
/// or a Mutex in order to prevent the condition where the call completes
/// successfully, but the timer fires prior to calling "CallComplete"
/// thus firing the FailureAction after the success of the call.
/// </summary>
public void CallComplete()
{
if ((DefaultTimeout != TimeSpan.Zero) && myTimer.IsEnabled)
myTimer.Stop();
}
private void myTimer_Tick(object sender, EventArgs e)
{
CallComplete();
if (myFailureAction != null)
myFailureAction();
}
/// <summary>
/// WPF-friendly timer for use in aborting "Async" Webservice calls
/// </summary>
private DispatcherTimer myTimer;
/// <summary>
/// The call to be made
/// </summary>
private Action myAsyncCall;
/// <summary>
/// What action the helper should take upon a failure
/// </summary>
private Action myFailureAction;
}
}
the MainWindow.xaml.cs
file with the relvant code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ASPSandcastleWPFClient.ASPSandcastleWebserviceClient;
namespace ASPSandcastleWPFClient
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ASPSandcastleWebservice myClient = null;
private AsyncCallHelper myHelloWorldHelper = null;
public MainWindow()
{
InitializeComponent();
}
private void InitializeClient()
{
myClient = new ASPSandcastleWebservice();
myHelloWorldHelper =
new AsyncCallHelper
(
myClient.HelloWorldAsync,
HelloWorldTimeout
);
}
private void Window_Initialized(object sender, EventArgs e)
{
InitializeClient();
}
/// <summary>
/// this is called prior to making a call so that we do not end up with multiple
/// outstanding async calls
/// </summary>
private void DisableButtons()
{
btnStartAsyncCall.IsEnabled = false;
btnStartAsyncCallWithTimeout.IsEnabled = false;
}
/// <summary>
/// this is called after a result is received or the call is cancelled due to timeout
/// so that we know it's safe to make another call.
/// </summary>
private void EnableButtons()
{
btnStartAsyncCall.IsEnabled = true;
btnStartAsyncCallWithTimeout.IsEnabled = true;
}
private void btnStartAsyncCall_Click(object sender, RoutedEventArgs e)
{
DisableButtons();
// disable the timeout handling
AsyncCallHelper.DefaultTimeout = TimeSpan.Zero;
myClient.HelloWorldCompleted += new HelloWorldCompletedEventHandler(myClient_HelloWorldCompleted);
myHelloWorldHelper.BeginAsyncCall();
lblResponse.Content = "waiting...";
}
private void btnStartAsyncCallWithTimeout_Click(object sender, RoutedEventArgs e)
{
DisableButtons();
// enable the timeout handling
AsyncCallHelper.DefaultTimeout = TimeSpan.FromSeconds(10);
lblResponse.Content = "waiting for 10 seconds...";
myHelloWorldHelper.BeginAsyncCall();
}
/// <summary>
/// see note RE: possible multi-thread issues when not using WPF in AsyncCallHelper.cs
/// </summary>
private void HelloWorldTimeout()
{
myClient.CancelAsync(null);
lblResponse.Content = "call timed out...";
EnableButtons();
}
void myClient_HelloWorldCompleted(object sender, HelloWorldCompletedEventArgs e)
{
myHelloWorldHelper.CallComplete();
if (!e.Cancelled)
lblResponse.Content = e.Result;
EnableButtons();
}
}
}
I don't know whether it's idiomatic, but I also use a timer (a DispatchTimer
) from Silverlight when issuing async requests via WebClient.DownloadStringAsync(...)
.
What does the web service return? An XML, JSON, or other? Are you using this for like a web site? If so why don't you try to use jquery ajax call and you can then load it async and you can specify a time out with .ajax().
精彩评论