我做了一个小项目来演示如何实现这个功能;这并不像我想象的那么简单,但是,什么事情又是简单的呢?
整个项目包括一个 Web 服务和一个 WPF 客户端,其中有按钮用于调用带有和不带超时的服务
http://www.mediafire.com/file/3xj4o16hgzm139a/ASPWebserviceAsyncTimeouts.zip。下面是一些相关片段。我使用了 DispatcherTimer 类来实现超时,代码中描述了它的使用方法;这个对象显然很适合 WPF,并且(应该)可以解决可能遇到的所有同步问题。
注意:可能可以通过 WCF 风格的“服务引用”来完成,但是,我无法找到方法,走了很多弯路。最后,我选择了一个更旧的“Web 引用”(可以通过“添加服务引用...”菜单、选择“高级”按钮,然后选择“添加 Web 引用”来找到)。
我选择使用helper类的原因是为了演示如果你有很多调用,如何处理跟踪所有内容。如果没有这个,迅速混乱。此外,可能有可能得到一个更通用的版本,几乎所有处理都可以在代码中完成,但WCF代码占用了我大部分时间,由于服务引用代码中使用泛型的方式,这种处理方式不适用。我只是快速查看了Web服务代码,看起来更有可能实现,但不幸的是我没有足够的时间去尝试。如果您希望我进一步查看,我会尽力而为。
现在继续!;)
用于执行回调的helper: AsyncCallHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Windows.Threading;
namespace ASPSandcastleWPFClient
{
public class AsyncCallHelper
{
#region Static Defaults
private static TimeSpan myDefaultTimeout;
public static TimeSpan DefaultTimeout
{
get
{
return myDefaultTimeout;
}
set
{
if ((value == null) || (value < TimeSpan.Zero))
myDefaultTimeout = TimeSpan.Zero;
else
myDefaultTimeout = value;
}
}
#endregion
public AsyncCallHelper(Action AsyncCall, Action FailureAction)
{
myAsyncCall = AsyncCall;
myFailureAction = FailureAction;
myTimer = new DispatcherTimer();
myTimer.Interval = DefaultTimeout;
myTimer.Tick += new EventHandler(myTimer_Tick);
}
public void BeginAsyncCall()
{
myAsyncCall();
if (DefaultTimeout > TimeSpan.Zero)
{
myTimer.Interval = DefaultTimeout;
myTimer.Start();
}
}
public void CallComplete()
{
if ((DefaultTimeout != TimeSpan.Zero) && myTimer.IsEnabled)
myTimer.Stop();
}
private void myTimer_Tick(object sender, EventArgs e)
{
CallComplete();
if (myFailureAction != null)
myFailureAction();
}
private DispatcherTimer myTimer;
private Action myAsyncCall;
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
{
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();
}
private void DisableButtons()
{
btnStartAsyncCall.IsEnabled = false;
btnStartAsyncCallWithTimeout.IsEnabled = false;
}
private void EnableButtons()
{
btnStartAsyncCall.IsEnabled = true;
btnStartAsyncCallWithTimeout.IsEnabled = true;
}
private void btnStartAsyncCall_Click(object sender, RoutedEventArgs e)
{
DisableButtons();
AsyncCallHelper.DefaultTimeout = TimeSpan.Zero;
myClient.HelloWorldCompleted += new HelloWorldCompletedEventHandler(myClient_HelloWorldCompleted);
myHelloWorldHelper.BeginAsyncCall();
lblResponse.Content = "waiting...";
}
private void btnStartAsyncCallWithTimeout_Click(object sender, RoutedEventArgs e)
{
DisableButtons();
AsyncCallHelper.DefaultTimeout = TimeSpan.FromSeconds(10);
lblResponse.Content = "waiting for 10 seconds...";
myHelloWorldHelper.BeginAsyncCall();
}
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();
}
}
}