• 使用Task简化Silverlight调用Wcf


    从.Net4.0开始,.Net提供了一个Task类来封装一个异步操作,用来简化异步方法的调用。.Net4.5更进一步,添加了async和await两个关键字,异步编程同步化,不用再写一堆散乱的回调或者完成事件处理。Silverlight5开始支持Task类,但是要用await的话就需要编译器的支持,VS2012直接支持,如果是VS2010,那就要安装Async CTP,而Silverlight4没有Task类,但安装Async CTP后,得到的AsyncCtpLibrary_Silverlight.dll会带有适合Silverlight4使用的Task类。下面介绍怎么使用。

    使用Task封装异步方法

    可能大家都知道,Task不是一个静态的工具类,我们不是调用它的一个静态方法来简化异步的调用,而是首先要将异步操作封装到一个Task中,然后再使用这个封装好的Task,一个Task就包含了开始和回调(或者是开始和完成事件处理),这样对于Task的消费者来说,异步操作的复杂性就隐藏在Task的背后了。TaskFactory<TResult>类能帮助我们快速封装异步方法到一个Task中。例如:

    public Task<UserInfo> CreateUserTaskAsync(UserInfo user)
    {
        return Task<UserInfo>.Factory.FromAsync(this.Channel.BeginCreateUser, this.Channel.EndCreateUser, user, null);
    }

    对于Wcf服务的方法,我暂时发现微软提供了两种方式自动生成这些基于Task方法。

    1.VS2012+.Net4.5

    VS2012下.Net4.5的项目在添加服务引用时有如下设置可以生成基于Task的方法

    image

    生成后的XXXAsync方法的返回类型就是Task<T>。例如:

    public System.Threading.Tasks.Task<string> SayHelloToAsync(string name)
    {
        return base.Channel.SayHelloToAsync(name);
    }

    2.Net4.0+TaskWsdlImportExtension.dll

    如果使用VS2010或者在2012下的项目的目标框架是.Net4.0的话,是没有上面那个选项的。这时可以借助安装Async CTP后得到的TaskWsdlImportExtension.dll来帮助生成。首先对应的项目中引用TaskWsdlImportExtension.dll,然后在web.config中添加如下配置:

    <system.serviceModel>
        <client>
            <metadata>
                <wsdlImporters>
                    <extension type="TaskWsdlImportExtension.TaskAsyncWsdlImportExtension, TaskWsdlImportExtension" />
                </wsdlImporters>
            </metadata>
        </client>
    </system.serviceModel>
    

    更新服务引用后,同样得到基于Task的异步方法。

    很可惜的是这个dll不适合Silverlight使用,所以我参考TaskWsdlImportExtension的封装方式(使用Task.Factory.FromAsync),自己做了个小工具来生成这些方法。

    自定义工具生成Silverlight项目的基于Task的操作

    这个工具本来想写成T4模版的,但对T4不是很熟,最后写成了一个exe。

    大概原理如下,首先遍历项目下的所有叫Reference.cs的文件,动态把它编译为Assembly,再反射这个Assembly,找那些BeginXXX方法,把这些方法封装为Task方法放到一个跟原Service同命名空间,同一一个Client的部分类中。

    使用方式可能有点麻烦,但个人认为可以接受。

    1.在有服务引用的Silverlight类库中添加一个类

    image

    2.右键这个类,选择打开方式,添加,选择小工具的exe

    image

    然后确定,即可生成一堆XXXTaskAsync方法。

    当然,上面是第一次使用,以后使用时,打开方式上存在这个程序,只需右击打开方式,再在列表选择这个程序两步即可。

    image

    生成的代码大概如下:

    image

    下面再看看怎么使用这些方法。

    1.使用async和await

    Silverlight项目使用vs2012或者VS2010安装了Async CTP的话,就可以使用async和await。

    1.vs2010的话首先在在Async CTP下找到AsyncCtpLibrary_Silverlight.dll或者AsyncCtpLibrary_Silverlight5.dll,并添加引用

    vs2012就在Nuget里搜索await,引用下面的这个包

    image

    1.普通使用

    await的使用方式可能大家都知道,就不详细介绍,就是在方法里加async关键字,在Task方法前加await关键字

    async private void OnMainPageLoaded(object sender, RoutedEventArgs e)
    {
        var users = await userServiceClient.GetAllTaskAsync();
        dataGrid1.ItemsSource = users;
    }
    async private void OnMultiTaskClick(object sender, RoutedEventArgs e)
    {
        var num = await systemServiceClient.GetNumTaskAsync(3);
        var result = await systemServiceClient.SayHiToTaskAsync(num.ToString("n2"));
        var time = await systemServiceClient.GetSerivceTimeTaskAsync();
        var users = await userServiceClient.GetAllTaskAsync();
        MessageBox.Show(string.Format("Num:{1},{0}{2},{0}Time:{3},{0}UserCount:{4}", Environment.NewLine,
                            num.ToString("n2"),
                            result,
                            time.ToString(),
                            users.Count.ToString()));
    }

    可以看到OnMultiTaskClick的第二个wcf方法是依赖于第一个wcf方法的结果的,这里感觉完全是”同步编程“,结果没有问题:

    image

    2.Debug

    使用await做Debug时跟同步编程一样,无论单步或是其它,得心应手:

    image

    3.异常

    调用wcf时产生的异常信息也跟不使用Task时一样的,对于Wcf返回来的异常类型是FaultException<ExceptionDetail>。把null传到下面这个方法产生的异常信息。

    [OperationContract]
    public string SayHiTo(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentNullException("name");
    
        return string.Format("Hi,{0}!", name);
    }
    image

    2.后台线程+Task.Wait

    如果没有VS2012或者不想安装Async CTP的话,就可以使用这种方式。由于Task.Wait是会阻塞线程的,在Silverlight的主线程是不允许这样做的,所以把操作放到后台线程去做,既然是后台线程,所以就会存在跨线程操作UI的问题,要记得使用Dispatcher.BeginInvoke来操作UI。

    1.普通使用

    private void OnMainPageLoaded(object sender, RoutedEventArgs e)
    {
        ThreadPool.QueueUserWorkItem((s) =>
        {
            var task = userServiceClient.GetAllTaskAsync();
            task.Wait();
            Dispatcher.BeginInvoke(() =>
            {
                dataGrid1.ItemsSource = task.Result;
            });
        });
    }
    private void OnMultiTaskClick(object sender, RoutedEventArgs e)
    {
        ThreadPool.QueueUserWorkItem((s) =>
        {
            var task1 = systemServiceClient.GetNumTaskAsync(5);
            task1.Wait();
            var task2 = systemServiceClient.SayHiToTaskAsync(task1.Result.ToString());
            var task3 = systemServiceClient.GetSerivceTimeTaskAsync();
            var task4 = userServiceClient.GetAllTaskAsync();
            task2.Wait();
            task3.Wait();
            task4.Wait();
            Dispatcher.BeginInvoke(() =>
            {
                MessageBox.Show(string.Format("Num:{1},{0}{2},{0}Time:{3},{0}UserCount:{4}", Environment.NewLine,
                    task1.Result.ToString(), 
                    task2.Result, 
                    task3.Result.ToString(), 
                    task4.Result.Count.ToString()));
            });
        });
    }

    跟上面一样,OnMultiTaskClick的第二个方法依赖于第一个方法,结果雷同。

    image

    这种方法感觉没有上面的await那么好,但也比直接编写的APM(Begin/End)或者EAP(完成事件)要方便不少。

    2.Debug

    使用这种方式Debug时要把断点放到委托内,其它一样,Wait后得到结果。

    image

    3.异常

    这里调用wcf产生的异常有点不一样,原本的异常变成了AggregateException,而真正的FaultException<ExceptionDetail>跑到了InnerException里去了

    image

    使用这个方式还有一点要注意,调用wcf的产生异常跳到Application_UnhandledException时也是后台线程,需要使用Deployment.Current.Dispatcher.BeginInvoke来操作UI,例如:

    private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
    {
        var msgFormat = "Message:{0} {1} StackTrace:{2}";
        var msg = e.ExceptionObject.Message;
        var stackTrace = e.ExceptionObject.StackTrace;
        if (e.ExceptionObject.InnerException != null && e.ExceptionObject.InnerException is FaultException<ExceptionDetail>)
        {
            var ex = e.ExceptionObject.InnerException as FaultException<ExceptionDetail>;
            msg = ex.Detail.Message;
            stackTrace = ex.Detail.StackTrace;
        }
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            MessageBox.Show(string.Format(msgFormat, msg, System.Environment.NewLine, stackTrace));
        });
        e.Handled = true;
    }

    总结

    1.对于生成Task方法的方式,如果是.Net项目可以选择微软提供的方法,VS2012+.Net4.5直接在服务引用生成,其它使用TaskWsdlImportExtension.dll。

    2.对于Silverlight项目的话,只能自己制造工具去包装了。

    3.条件满足的话尽量使用await的方式来使用Task(VS2012或者VS2010+Async CTP)

    4.使用VS2010又不想安装Async CTP就用后台线程+Task.Wait的方式,多注意后台线程不能调用UI的问题和异常处理的一点差异。

    工具源码+测试Task测试代码

  • 相关阅读:
    cb快捷键
    N的阶乘的长度 V2(斯特林近似)
    最大子序列和(Max Sum ,Super Jumping! Jumping! Jumping! )
    关于莫比乌斯和莫比乌斯反演
    最少拦截系统
    set用法详解
    几种数学公式(环排列 母函数 唯一分解定理 卡特兰数 默慈金数 贝尔数 那罗延数)
    最小堆算法
    并查集算法
    dijkstra算法演示
  • 原文地址:https://www.cnblogs.com/lemontea/p/2810549.html
Copyright © 2020-2023  润新知