65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET 中的异步 Web 服务调用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (14投票s)

2013年6月3日

CPOL

8分钟阅读

viewsIcon

175359

本文档描述了如何通过利用异步调用机制来高效且有效地使用 Web 服务方法调用。

引言

在深入探讨这个主题之前,我想先描述一个场景。

在一个应用程序中,我正在使用一个执行大量处理的 Web 服务方法,
它大约需要 3 到 4 分钟才能完成,
比如一个 IO 操作,如写入文本文件或 XML 文件之类的,
而且,在 Web 服务将所有这些数据写入文件后,调用应用程序本身必须执行一些耗时的工作,例如从文件读取数据或获取数据,
并处理获取到的数据,这也需要大约 5 到 6 分钟才能完成……
那么在我们的应用程序中通常会发生什么呢……?
就像所有其他应用程序服务调用一样,执行任务的线程(主线程)将等待 Web 服务调用完成并返回输出,对吗?
如果我们可以通过一个单独的线程调用 Web 服务方法,会发生什么呢?
就像一个新线程执行服务,而主线程继续执行程序的其余部分……
执行完成后,Web 服务回调调用应用程序并告知
我已经完成我的工作了……
听起来不错,对吧?
是的,这确实是一个很棒的机制。
在 ASP.NET 中,我们有一种机制可以异步调用 Web 服务。
这将有助于处理大量请求,通过释放线程而不让它们等待处理请求来处理这些请求。

我们如何实现这一点呢?

我们将在 ASP.NET 中借助异步 Web 服务调用机制来实现这一点。
此机制是在 .NET Framework 2.0 中引入的。

这里我将通过一个简单的例子来解释。

示例项目

我将创建一个简单的 Web 服务,其中包含一个名为 MyTestAsynchronousMethod 的服务方法。它接受两个输入参数:namewait time
输出是一个字符串,例如 "Hei...XXXX..Iam Called Asynchronously"
该字符串将在您提供作为等待时间参数的等待时间之后返回。
所以要求很明确,对吗……?

让我们从项目开始……
打开 Visual Studio……
它将创建一个名为 MyAsync.asmx 的 Web 服务,其中包含一个示例 Web 方法。
您可以在下面看到 Web 方法的代码片段:

[WebMethod]
public string MyTestAsynchronousMethod(string strName, int
waitTime)
{
  System.Threading.Thread.Sleep(waitTime);
  return "Hei..." + strName + "Iam Called Asynchronously"; 
}

通过查看 片段 1,您将了解 Web 服务方法正在做什么,对吗?
我们还创建了一个使用者 ASP.NET Web 应用程序,如下图所示。

它非常简单,有两个文本框。
一个用于输入等待时间,另一个用于输入您的姓名……还有一个按钮,
我还放置了一个标签来显示 Web 服务调用的结果。

我们已经完成了调用应用程序和服务。
现在我们唯一需要做的就是在按钮点击事件中调用 Web 服务。

要在按钮点击事件中调用应用程序,我们必须创建一个代理。
众所周知,我们可以使用 WSDL.EXE 创建代理,也可以通过向调用应用程序添加 Web 引用来创建代理。
这里我将使用第二种方法,即使用服务引用以方便。

右键单击调用 Web 应用程序,
转到“添加服务引用”选项,
然后继续在您的调用应用程序中添加服务引用。
我已在调用应用程序中添加了一个名为 MyProxy 的 Web 引用。

所以我们已经准备好从按钮点击中调用服务了……

作为第一步,在异步按钮点击中,
我将创建代理类对象,并且通过 IntelliSense,我们可以看到与我们创建的 Web 方法相关的主要是三件事。
一个事件和两个方法,其中一个带有 async 参数。

这里我们将使用名为 MyTestAsynchronousMethodCompleted 的事件和 Web 方法 MyTestAsynchronousMethodAsync

您可能会想,Web 方法 MyTestAsynchronousMethodAsync 和事件 MyTestAsynchronousMethodCompleted 是从何而来,对吗?
答案是,它们在创建 Web 服务代理时会自动生成。

那么这里就产生了一个问题
这个事件有什么作用?
以及
这个事件何时会被触发?

是的,这是一个有效的问题。
Web 方法 MyTestAsynchronousMethodAsync 是实现异步调用的主角,而事件 MyTestAsynchronousMethodCompleted 则是主角的配角。
当异步 Web 方法调用完成执行时,该事件将被触发……
希望您能理解这一点。

您可以在下面的快照中看到事件和方法。

怎么样……?

接下来我们需要做的是连接事件。
如前所述,该事件负责在Web服务方法执行后返回结果。
因此,我将在我们的调用应用程序中设置一个标签,使其显示从Web服务方法调用返回的字符串……
好的,让我们看看它是如何完成的……

为此,我为该事件添加了一个事件处理程序。
Visual Studio 会自动为我们创建事件处理程序存根。
只需在我们已有的事件旁边输入 +=,然后按两次键盘上的 Tab 键……很简单,
您已经获得了带有参数和参数设置的事件处理程序存根……
您唯一需要做的就是在事件处理程序存根中编写业务逻辑……这是 Visual Studio 不知道的,我的意思是您的业务逻辑。

所以我们必须在这个存根中编写代码,将结果赋值给标签……

您可以在事件参数中看到一个名为 Result 的属性。
这个属性会以字符串格式为您提供 Web 方法执行的 ResultError 等,它不再是服务方法本身了。

所以,让我们通过将结果分配给标签文本属性(您可以在下面的快照中看到)来实现存根中的逻辑。
好的,现在我们甚至分配了结果……
我们调用方法了吗……?
没有,对吧……?

我们去调用方法吧……
我们要调用哪个方法呢……?
我们将调用自动生成的第二个 Web 方法。

我想让您注意一件事,
我们没有像通常那样将 Web 方法结果赋给任何变量或对象……
您看到上面的截图了吗?
Web 方法的返回类型是 void
我们怎么能不把它赋给任何变量或对象呢……?
那是不可能的。
这就是为什么我们会在Web方法调用完成事件中获得结果,正如前面讨论的,在事件参数的result属性中。

这时您可能会想到一个问题,为什么会这样呢……?

答案是……我们只有在方法执行完成后才能获得输出。
在这种情况下,一旦方法被调用,主线程将内部将控制权转移到一个子线程,
它将负责Web服务方法的执行,而主线程将继续在我们的调用应用程序中执行过程。

因此,在Web服务方法执行完成后,事件将被触发,最终我们将获得结果。

希望一切都清楚了……

namespace AsyncConsumer
{
    public partial class _Default : System.Web.UI.Page
    {
        Stopwatch objSW = new Stopwatch();
        protected void Page_Load(object sender, EventArgs e)
        {
        }
        protected void BtnAsync_Click(object sender, EventArgs e)
        {
            objSW.Reset();
            objSW.Start();
            MyProxy.MyAsync objProxy = new MyProxy.MyAsync();
            objProxy.MyTestAsynchronousMethodCompleted += 
              new MyProxy.MyTestAsynchronousMethodCompletedEventHandler(
              objProxy_MyTestAsynchronousMethodCompleted);
            objProxy.MyTestAsynchronousMethodAsync(txtName.Text, 
              Convert.ToInt32(txtWaitTime.Text));
            DoSomeLongJob();
            lblExecTime.Text = "Total Execution Time :" + 
              objSW.ElapsedMilliseconds.ToString() + " Milliseconds";
        }
        void objProxy_MyTestAsynchronousMethodCompleted(object sender, 
             MyProxy.MyTestAsynchronousMethodCompletedEventArgs e)
        {
            lblResult.Text = e.Result;
        }
        private void DoSomeLongJob()
        {
            System.Threading.Thread.Sleep(5000);
        }
    }
} 

我还添加了一个名为 DoSomeLongJob() 的方法。它使线程等待 5 秒钟以供调用应用程序使用,并使用一个标签来显示执行时间。
以上是我们完成的完整代码片段。

让我们看看我们应用程序的执行情况。

在此之前,让我们试着预测输出和执行时间……

如果我们将此作为普通的 Web 方法使用,并且我输入如下内容:

结果和执行时间会是多少?
在正常情况下,这将大约延迟 7 秒,即我们给出的 2 秒,以及 DoSomeLongJob(); 的 5 秒
对吗?

让我们看看需要多长时间。

哎呀!!!不幸的是,或者幸运的是,当我点击按钮时,我得到了一个如下所示的异常

你注意到异常中标记的部分了吗?
是的,我们必须在计划进行任何异步 Web 服务方法调用的页面的 Page 指令中添加并将 Async 属性设置为 true
否则是不可能的。我们去设置一下。

设置 Async 属性后,指令将变为

好的,我们再试一次……这次希望能顺利得到结果,没有异常……

哇哦哦哦哦!!!
查看上面的快照……
我们的应用程序成功执行,并且请看执行时间……
我们用了 5000 毫秒而不是 7000 毫秒完成了这项任务。
节省了大约 2000 毫秒的执行时间,这真是太棒了。

在这种情况下,如果我们使用普通的Web方法调用机制,它肯定会花费7000毫秒以上……

结论 

通过实现异步Web服务调用等机制,我们可以节省大量的执行时间。
这对于调用大量 Web 服务并执行长时间运行作业的应用程序将非常有用。
时间就是金钱,节省时间就是节省金钱……通过使用异步Web方法调用。

© . All rights reserved.