Я разработал небольшой проект, который демонстрирует, как это сделать; это было не так просто, как я предполагал, но что тогда будет?
Вот весь проект с веб-сервисом, а также клиентом в 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();
}
}
}