使用 Web 服务的异步方法

Posted

技术标签:

【中文标题】使用 Web 服务的异步方法【英文标题】:usage of async methods for webservice 【发布时间】:2012-10-23 09:39:07 【问题描述】:

我想编写一个小的 Silverlight 应用程序,其中有三个 DataGrid。 每个 Datagrid 都使用异步方法从 Web 服务获取数据。 现在,我希望第一个数据网格从 web 服务获取数据,而不是从 web 服务获取第二个数据网格的数据,其中参数来自第一个数据网格中选定行的参数,第三个数据网格具有前两个数据网格的参数。 第一个数据网格通过注册事件处理程序获取 MainPage 方法中的数据,然后使用异步方法。

现在我的问题是我在 SelectionChanged(事件处理)方法中对其他数据网格使用异步方法,我猜这是错误的概念,因为在数据网格 2 中选择了某些内容并返回到数据网格 1 之后数据网格消失。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ServiceModel;
using Rebat.SymptomeService;


namespace Rebat

    public partial class MainPage : UserControl
    
        ServiceClient client = new ServiceClient();

        public MainPage()
        
            InitializeComponent();
            ServiceClient client = new ServiceClient();
           client.SymptomeListCompleted += new EventHandler<SymptomeListCompletedEventArgs>(client_SymptomeListCompleted);
            client.SymptomeListAsync();
        


        void client_SymptomeListCompleted(object sender, SymptomeListCompletedEventArgs e)
        
            CustomerGrid.ItemsSource = e.Result;
        
        void client_CustomerListCompleted(object sender, CustomerListCompletedEventArgs e)
        
           CustomerGrid2.ItemsSource = e.Result;
        
        void client_SalzListCompleted(object sender, SalzListCompletedEventArgs e)
        
            SalzGrid.ItemsSource = e.Result;
        

        private void CustomerGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        
            Symptome sympt = CustomerGrid.SelectedItem as Symptome;
            client.CustomerListCompleted += new EventHandler<CustomerListCompletedEventArgs>(client_CustomerListCompleted);
            client.CustomerListAsync(sympt.sId.ToString());

        

        private void CustomerGrid2_SelectionChanged(object sender, SelectionChangedEventArgs e)
        
            Symptome2 sympt2 = CustomerGrid2.SelectedItem as Symptome2;
            client.SalzListCompleted += new EventHandler<SalzListCompletedEventArgs>(client_SalzListCompleted);
            //this is the PROBLEM:
            client.SalzListAsync(sympt2.sy1.ToString(), sympt2.sy2.ToString());
        

    

我必须改变什么,或者我必须如何使用异步方法? 您可以在事件处理方法中使用异步方法吗?这是否适用于使用网络服务?

【问题讨论】:

这是使用 WCF 还是 ASMX?以及 Silverlight 和 .NET 的哪些版本? 这是 WCF。 Silverlight 5 和 .Net 4.5 我猜,但这有关系吗? 除其他外,在 .NET 4.5 中,您可以使用 await 关键字,这可以为您简化事情。 【参考方案1】:

假设您可以使用 .NET 4.5 异步功能,如果您将 serviceproxy 调整为基于任务的异步模式,则执行此类调用会简单得多。 您可以在事件处理程序中使用异步方法,您只需将其设为异步即可充分受益于新的 async/await 关键字。

要调整你的代理,首先声明一个辅助静态类:

public static partial class TAPExtensions

    public static void HandleCompletion<T>(TaskCompletionSource<T> tcs, AsyncCompletedEventArgs args, Func<T> getResult, Action unregisterHandler = null)
    
        // Transfers the results from the AsyncCompletedEventArgs and getResult() to the 
        // TaskCompletionSource, but only AsyncCompletedEventArg's UserState matches the TCS 
        // (this check is important if the same WebClient is used for multiple, asynchronous 
        // operations concurrently).  Also unregisters the handler to avoid a leak. 
        if (args.UserState == tcs)
        
            if (args.Cancelled) tcs.TrySetCanceled();
            else if (args.Error != null) tcs.TrySetException(args.Error);
            else tcs.TrySetResult(getResult());
            if (unregisterHandler != null) unregisterHandler();
        
    

现在,创建另一个帮助类来扩展您的代理。为每个操作创建一个适配器方法:

public static class ServiceClientEx

    public static async Task<IEnumerable<ReturnType>> SalzListTask(this ServiceClient proxy, string arg1, string arg2)
    
        var tcs = new TaskCompletionSource<IEnumerable<ReturnType>>();
        EventHandler<SalzListCompletedEventArgs> handler = (s, args) => TAPExtensions.HandleCompletion(tcs, args, () => args.Result);
        proxy.SalzListCompleted += handler;

        try
        
            proxy.SalzListAsync(arg1, arg2, tcs);
            return await tcs.Task;
        
        finally  proxy.SalzListCompleted -= handler; 
    

现在您可以在代码中使用扩展方法(假设您为 ServiceClientEx 类的命名空间添加了“使用”指令)。结果会是这样的:

public partial class MainPage : UserControl

    ServiceClient client = new ServiceClient();

    public MainPage()
    
        InitializeComponent();
        ServiceClient client = new ServiceClient();
    


    private async void CustomerGrid2_SelectionChanged(object sender, SelectionChangedEventArgs e)
    
        Symptome2 sympt2 = CustomerGrid2.SelectedItem as Symptome2;

        alzGrid.ItemsSource = await client.SalzListTask(sympt2.sy1.ToString(), sympt2.sy2.ToString());
    

注意在事件处理程序声明中使用“async”关键字。它将异步行为传播到该方法:一旦选择更改,处理程序将正常执行,直到它到达第一个等待,然后它将返回到原始调用者(控件的调度程序)。调用结束时,继续(将 itemssource 设置为操作结果)将在 UIThread 中执行。

您甚至可以包装调用以检查异常,例如:

private async void CustomerGrid2_SelectionChanged(object sender, SelectionChangedEventArgs e)
    
        try
            Symptome2 sympt2 = CustomerGrid2.SelectedItem as Symptome2;

            alzGrid.ItemsSource = await client.SalzListTask(sympt2.sy1.ToString(), sympt2.sy2.ToString());
        catch(MyException ex)
            MessageBox.Show(ex.ToString());
        
    

一些建议:

我认为从构造函数触发异步调用不是一个好主意。例如,您可以在控件的 Loaded 事件中进行一些初始化后触发它(更好的方法是在 ViewModel 中执行此类操作,遵循 MVVM 模式并仅从您的 UI 中使用它,但这是另一个主题) . 按照您现在的操作方式,每次选择更改时都会向您的代理添加一个新的处理程序,因此您的处理程序将被执行多次。 尝试在每次调用时实例化您的代理,因为如果代理出现故障,实例将变得不可用。

【讨论】:

以上是关于使用 Web 服务的异步方法的主要内容,如果未能解决你的问题,请参考以下文章

从 Web 服务下载异步数据并存储在核心数据中的最佳方法

创建异步 Web 服务方法

从异步 Web 服务响应更新托管对象的最佳方法?

Web API 服务 - 如何在异步任务中使用“HttpContext.Current”

来自服务实现的 Google Web Toolkit 异步调用

使用 Moq 模拟单元测试的异步方法