Как определить тайм-аут на стороне клиента при асинхронном вызове веб-службы ASP.NET? - PullRequest
3 голосов
/ 28 июня 2011

Сегодня я некоторое время искал конкретный случай для вызова (внешнего) веб-сервиса ASP.NET со следующими требованиями:

  • Вызовы должны выполняться асинхронно
  • Тайм-аут должен быть реализован, потому что выполнение веб-службы может занять много времени

В Интернете и в StackOverflow появляется много вопросов на эту тему, но они либо датированы, либо предлагают использовать свойство WebRequest.TimeOutкоторый применим только для синхронных вызовов.

Одна альтернатива - System.Threading.Timer.Запуск таймера непосредственно перед началом вызова и его отмена при достижении TimerCallback.

Однако я думаю, что в таких случаях должен быть более распространенный подход.К сожалению, не смог найти его до сих пор.У кого-нибудь есть идея установить тайм-ауты на стороне клиента для вызовов асинхронных веб-служб?

Заранее спасибо.

Ответы [ 5 ]

8 голосов
/ 04 июля 2011

Действительно, вы не всегда можете использовать WebRequest.TimeOut для асинхронных операций; по крайней мере, не для всех разработчиков абстрактного класса WebRequest. Например, в msdn задокументировано, что это свойство игнорируется при вызове HttpWebRequest.BeginGetResponse для запуска асинхронной операции. В явном виде указывается, что свойство TimeOut игнорируется и что пользователь несет ответственность за реализацию поведения тайм-аута, если это необходимо.

В примере кода, поставляемого с документацией HttpWebRequest.BeginGetResponse для msdn , ManualResestEvent allDone в сочетании с WaitOrTimerCallback используется следующим образом:

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 .

Суть в том, что вы должны реализовать это самостоятельно.

8 голосов
/ 08 июля 2011

Пожалуйста, проверьте ваш 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, и выдобавляем сервисную ссылку (не веб-ссылку).

2 голосов
/ 11 июля 2011

Я разработал небольшой проект, который демонстрирует, как это сделать; это было не так просто, как я предполагал, но что тогда будет?

Вот весь проект с веб-сервисом, а также клиентом в WPF, в котором есть кнопки для вызова как с тайм-аутом, так и без него http://www.mediafire.com/file/3xj4o16hgzm139a/ASPWebserviceAsyncTimeouts.zip. Я приведу некоторые соответствующие фрагменты ниже. я использовал класс DispatcherTimer, как описано в коде для выполнения тайм-аутов; похоже, что этот объект, по-видимому, дружествен к WPF и (должен) устранять большинство, если не все проблемы синхронизации, которые могли бы возникнуть в противном случае.

ПРИМЕЧАНИЕ: это, вероятно, может быть каким-то образом сделано с помощью "Service References" в стиле WCF, однако я не смог выяснить путь и попал в тупик. в конце концов я остановился на более старом стиле «Веб-ссылка» (к которому можно перейти, перейдя в «Добавить ссылку на службу ...», выбрав кнопку «Дополнительно», а затем выбрав «Добавить веб-ссылку».

причина, по которой я пошел с классом помощника, состояла в том, чтобы продемонстрировать, что можно сделать, если у вас много вызовов; отслеживание всего было бы очень грязным, если бы у нас этого не было. также, возможно, было бы возможно получить более общую версию, где почти вся обработка могла бы быть сделана в коде, но код WCF, который занимал большую часть моего времени, не поддается такой обработке из-за того, как универсальные используется в сервисном справочном коде. я только быстро просмотрел код веб-службы, и, скорее всего, это можно было сделать, но, к сожалению, у меня не было достаточно времени, чтобы поиграть с этой частью вещей. дайте мне знать, если вы хотите, чтобы я еще посмотрел, и я посмотрю, что я могу сделать.

теперь с шоу! ;)

помощник для выполнения обратного вызова: 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();
        }
    }
}
1 голос
/ 28 июня 2011

Я не знаю, является ли это идиоматическим, но я также использую таймер (DispatchTimer) от Silverlight при выдаче асинхронных запросов через WebClient.DownloadStringAsync(...).

0 голосов
/ 11 июля 2011

Что возвращает веб-сервис?XML, JSON или другое?Вы используете это как веб-сайт?Если да, то почему бы вам не попробовать использовать jquery ajax call, а затем загрузить его асинхронно и указать время ожидания с помощью .ajax ().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...