如何定义异步 ASP.NET Web 服务调用的客户端超时?

发布于 2024-11-17 20:13:01 字数 408 浏览 3 评论 0原文

今天,我搜索了一些特定的案例,用于调用(外部)ASP.NET Web 服务,并满足以下要求:

  • 调用必须异步完成
  • 必须实现超时,因为 Web 服务可能需要很长时间才能

执行互联网和 StackOverflow 上出现了许多关于此主题的问题,但要么已过时,要么建议使用仅适用于同步调用的 WebRequest.TimeOut 属性。

一种替代方法是使用 System.Threading.Timer。在开始调用之前启动计时器,并在到达 TimerCallback 时取消计时器。

然而,我认为应该有一个更通用的方法来处理此类情况。不幸的是到目前为止还没有找到它。有人知道如何在异步 Web 服务调用上设置客户端超时吗?

提前致谢。

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 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

断念 2024-11-24 20:13:01

请检查您的 app.config,它会有一些 servicemodel 设置,并且有各种可以配置的值。

当我添加新的服务引用时,我可以在 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>

尝试再次删除并添加引用,确保您的应用程序的目标框架是 4.0 并且您正在添加服务引用(不是 Web 引用)。

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).

岛徒 2024-11-24 20:13:01

事实上,您不能总是使用 WebRequest.TimeOut 来进行异步操作;至少对于抽象 WebRequest 类的所有实现者来说不是这样。例如,msdn 上记录了此属性 时被忽略调用 HttpWebRequest.BeginGetResponse 启动异步操作。明确指出 TimeOut 属性将被忽略,并且用户有责任在需要时实现超时行为。

HttpWebRequest.BeginGetResponse 附带的示例代码中 msdn 文档中,ManualResestEvent allDoneWaitOrTimerCallback 结合使用,如下所示:

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();

请参阅 msdn 上的完整示例

最重要的是你必须自己实现这一点。

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.

留蓝 2024-11-24 20:13:01

我做了一个小项目来演示如何做到这一点;事情并不像我想象的那么简单,但是,到底是什么?

这是带有 Web 服务的整个项目,还有 WPF 中的客户端,其中有一个用于调用带超时和不带超时的按钮 http://www.mediafire.com/file/3xj4o16hgzm139a/ASPWebserviceAsyncTimeouts.zip。我将在下面放置一些相关的片段。我使用 DispatcherTimer 类(如代码中所述)进行超时;看起来这个对象显然是 WPF 友好的,并且(应该)缓解大多数(如果不是全部)否则可能会遇到的同步问题。

注意:它可能可以通过 WCF 风格的“服务引用”以某种方式完成,但是,我无法找出方法并遇到了很多死胡同。我最终选择了旧样式的“Web 引用”(您可以通过转到“添加服务引用...”,选择“高级”按钮,然后选择“添加 Web 引用”来访问它。

我的原因是与帮助程序类一起使用的目的是演示在有很多调用的情况下可以做什么;如果我们没有它,那么跟踪所有内容很快就会变得混乱,可能会得到一个更通用的版本。几乎所有的处理都可以在代码中完成,但是 WCF由于服务参考代码中使用泛型的方式,花费了我大部分时间的代码并不适合这种处理,我只是快速查看了 Web 服务代码,而且看起来更有可能做到这一点。 ,但不幸的是,我没有足够的时间来研究这部分内容,请告诉我,我会看看我

现在可以做些什么!

进行回调的助手: 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;
    }

}

MainWindow.xaml.cs 文件包含相关代码:

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 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();
        }
    }
}
太阳公公是暖光 2024-11-24 20:13:01

我不知道这是否惯用,但在通过 WebClient.DownloadStringAsync(...) 发出异步请求时,我还使用了 Silverlight 中的计时器(DispatchTimer)。

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(...).

东北女汉子 2024-11-24 20:13:01

Web 服务返回什么? XML、JSON 还是其他?您是否将其用于网站之类的用途?如果是这样,为什么不尝试使用 jquery ajax 调用,然后可以异步加载它,并且可以使用 .ajax() 指定超时。

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().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文