.NET服务端开发—多线程使用小结(多线程使用常识)

有一段时间没有更新博客了,最近半年都在着写书《.NET框架设计—大型企业级框架设计艺术》,很高兴这本书将于今年的10月份由图灵出版社出版,有关本书的具体介绍等书要出版的时候我在另写一篇文行做介绍。可以先透露一下,本书是博主多年来对应用框架学习的总结,里面包含了十几个重量级框架模式,这些模式都是我们目前所经常使用到的,对于学习框架和框架开发来说是很好的参考资料,大家敬请期待。

好了,进入文章主题。

最近几个月本人一直从事着SOA服务开发工作,简单点讲就是提供服务接口的;从提供前端接口WEBAPI,到提供后端接口WCF\SOAFramework,期间学到了不少有关多线程使用上的经验,这些经验有的是本人自己的错误使用后的经验,有些是公司的前辈的指点,总之这些东西你不遇到过你是不会意识到该如何使用的,所以本人觉得很有必要总结分享给广大和我一样工作在一线的博友们。

我们从服务的处理环节为顺序来介绍:

1.使用入口线程来处理超长时间调用:

任何服务的调用都需要首先进到服务的入口方法中,该方法通常扮演着领域逻辑的门面接口(将系统用例进行服务接口的划分),通过该接口进行用例的调用。当我们需要处理长时间过程时都会面临着头疼的超时异常,如果我们再去设计如何做超时补偿措施就会很复杂而且是没有必要的开销。长时处理的服务调用场景多半在同步数据中,通过某个JobWs(工作服务)定期的来同步数据(本人就是在这个过程中学到的),当我们无法预知我们的服务会处理多长时间时,基本上都会首先去设置调用端的连接超时时间(是不是都会这么想?);这很正常,很来超时时间就是用来给我们用的;但是我们忽视了我们当前的业务场景了,如果你的服务不返回任何有关状态值的话“其实应该开启一个独立的线程来处理同步逻辑而让服务的调用者尽早收到相应”。

public class ProductApplicationService
    {
        public void SyncProducts()
        {
            Task.Factory.StartNew(() =>
            {
                var productColl = DominModel.Products.GetActivateProducts();
                if (!productColl.Any()) return; 

                DominModel.Products.WriteProudcts(productColl);
            });
        }
    }

这样就可以尽早解放调用者;通过开启一的单独的线程来处理具体的同步逻辑。

如果你的服务需要返回某个状态值怎么办?其实我们可以参考”异步消息架构模式“来将消息写入到某个消息队列中,然后客户端定期来取或者推送都可以,让当前的这个服务方法能够平滑的处理,至少为系统的整体性能瓶颈做了一份贡献。

1.1异常处理:

入口位置通常都会记录下调用的异常信息,也就是加上一个try{}catch{},用来捕获本次调用的所有异常信息。(当然你可能会说代码中充斥着try{}catch{}不是很好,可以将其放到某个看不见的地方自动处理,这有好有坏,看不见的地方我们就必然少不了配置,少不了对自定义异常类型的配置,总之事物都有两面性。)

public class ProductApplicationService
{
    public void SyncProducts()
    {
        try
        {
            Task.Factory.StartNew(() =>
            {
                var productColl = DominModel.Products.GetActivateProducts();
                if (!productColl.Any()) return; 

                DominModel.Products.WriteProudcts(productColl);
            });
        }
        catch(Exception exception)
        {
            //记录下来...
        }
    }
}

像这样,看上去好像没问题哦,但是我们仔细看看就会发现,这个try{}catch{}根本捕获不到我们任何异常信息的,因为这个方法是在我们开启的线程外面的,也就是说它早就结束了,开启的线程处理栈中根本就没有任何的try{}catch{}机制代码了;所以我们需要稍微调整一下同步代码来支持异常捕获。

public class ProductApplicationService
{
    public void SyncProducts()
    {
        Task.Factory.StartNew(SyncPrdoctsTask);
    } 

    private static void SyncPrdoctsTask()
    {
        try
        {
            var productColl = DominModel.Products.GetActivateProducts();
            if (!productColl.Any()) return; 

            DominModel.Products.WriteProudcts(productColl);
        }
        catch (Exception exception)
        {
            //记录下来...
        }
    }
}

如果你装了像Resharp这样的辅助插件的话会对你重构代码很有帮助,提取某一个方法会很方便快捷;

上述代码中,就在新开的线程中包含了异常捕获的代码;这样就不会导致你程序抛出很多未处理异常,在重要的逻辑点可能会丢失数据。不是说所有的异常都应该由框架来处理,我们需要自己手动的控制某个逻辑点的异常,这样我们可以保证我们自己的逻辑能够继续运行下去。有些逻辑是不可能因为异常的出现而终止整个处理过程的。

2.利用并行来提高多组数据的读取

位于SOA服务的最外层服务接口时,通常都需要包装内部众多服务接口来组合出外部需要的数据,此时需要查询很多接口的数据,然后等待数据都到齐了之后再将其统一的返回给前端。由于我有一段时间是专门给前端H5提供接口的,最让我感触的就是服务接口需要整合所有的数据给前端,从用户的角度讲不希望手机的界面还出现异步的现象吧,毕竟就那么大屏幕还有白的地方。但是这个需求给我们开发人员带来了问题,如果用顺序读取方式将数据都组合好,那个时间是人所无法接受的,所以我们需要开启并行来同时读取多个后端服务接口的数据(前提是你这些数据没有前后依赖关系)。

public static ProductCollection GetProductByIds(List<long> pIds)
{
    var result = new ProductCollection(); 

    Parallel.ForEach(pIds, id =>
    {
        //并行方法
    }); 

    return result;
}

一切看起来很舒服,多个ID同一个时间被一起运行,但是这里面有个坑。

2.1控制并行线程数:

如果我们用上述代码开启并行后,从GetProductByIds业务点来看一切会很顺利,而且效果很明显速度很快;但是如果当前GetProductByIds方法还在处理过程中时你再发起另一个服务调用时你就会发现服务器响应变慢了,因为所有的请求线程全部被占用了,这里Parallel并没有我们想的那么智能,能根据情况控制线程数;我们需要自己控制我们并行时的最大线程数,这样可以防止由于多线程被一个业务点占用而导致服务队列其他的后续请求(此时看CPU不一定很高,如果CPU过高导致不接受请求能理解,但是由于系统设置的问题让线程数不够用也是有可能的)

public static ProductCollection GetProductByIds(List<long> pIds)
{
    var result = new ProductCollection(); 

    Parallel.ForEach(pIds, new ParallelOptions() { MaxDegreeOfParallelism = 5 /*设置最大线程数*/}, id =>
    {
        //并行方法
    }); 

    return result;
}

2.2使用并行处理时数据的前后顺序是第一原则

这点上我犯了两次错,第一次是将前端需要的数据顺序打乱了,导致数据的排名出来问题;第二次是将写入数据库的同步数据的时间打乱了,导致程序无法再继续上次的结束时间继续同步。所以请大家一定要记住,当你使用并行时,首先问自己你当前的数据上下文逻辑在不在乎前后顺序关系,一旦开启并行后所有的数据都是无须的。

3.手动开启一个线程来代替并行库启动的线程

现在我们提供的服务接口多多少少会用到异步async,大概就是想让我们的系统能够提到点并发量,让宝贵的请求处理线程能够及时的被系统再利用而不是在等待上浪费。

大概代码会是这样的,服务入口:

public async Task<int> OperationProduct(long ids)
{
    return await DominModel.Products.OperationProduct(ids);
} 

业务逻辑:

public static async Task<int> OperationProduct(long ids)
{
    return await Task.Factory.StartNew<int>(() =>
      {
          System.Threading.Thread.Sleep(5000);
          return 100; 

          //其实这里开启的线程是请求线程池中的请求处理线程,说白了这样并不会提高并发等于没用。
      });
}

其实当我们最后开启了一个新线程时,这个新的线程和你awit的线程是同一种类型,这样并不会提高并发反而会由于频繁的切换线程影响性能。要想真的让你的async有实际意义,使用手动开启新线程来提高并发。(前提是你了解了当前系统的整体CPU和线程的比例,也就是说你开启一个两个手动线程是不会有问题的,但是你要放在并发的入口上就请慎重考虑)

在Task中开启手动线程有一点麻烦,看代码:

public async Task<int> OperationProduct(long id)
{
    var funResult = new AWaitTaskResultValues<int>();
    return await DominModel.Products.OperationProduct(id, funResult);
} 

public static Task<int> OperationProduct(long id, AWaitTaskResultValues<int> result)
        {
            var taskMock = new Task<int>(() => { return 0; });//只是一个await模拟对象,主要是让系统回收当前“请求处理线程” 

            var thread = new Thread((threadIds) =>
            {
                Thread.Sleep(7000); 

                result.ResultValue = 100; 

                taskMock.Start();//由于没有任何的逻辑,所以处理会很快完成。
            }); 

            thread.Start(); 

            return taskMock;
        }

之所以这么麻烦是为了让系统释放await线程而不是阻塞该线程。我通过简单的测试可以使用少量的线程来处理更多的并发请求。

作者:王清培

出处:http://blog.csdn.net/wangqingpei557

本文版权归作者和CSDN共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

.NET服务端开发—多线程使用小结(多线程使用常识)

时间: 2024-08-28 02:34:05

.NET服务端开发—多线程使用小结(多线程使用常识)的相关文章

WCF服务端开发和客户端引用小结

1.服务端开发(包含寄宿) 1.1 WCF服务创建方式 创建一个WCF服务,总是会创建一个服务接口和一个服务接口实现.通常根据服务宿主的不同,有两种创建方式. (1)创建WCF应用程序 通过创建WCF服务应用程序,可生成一个带.svc后缀名的文件.该文件与webform中的aspx文件类似,有后置代码,这些后置代码是服务接口的实现.这种方式创建的WCF服务都承载在IIS或WAS上. (2)创建WCF服务库 WCF服务库是一个类库,如前所述包含一个服务接口和服务实现,此种方式创建的WCF服务可根据

如何进行SuperMap iServer服务端开发

有时候在进行地图应用开发时,可能单纯的客户端无法满足要求(如功能和性能等因素),这时就需要进行iServer的服务端开发.SuperMap iServer 6R/7c的服务端开发步骤如下: 一.在Eclipse中新建一个Dynamic Web Project 此过程截图略去 二.引用iServer所需的各类jar包 经测试后发现,必须引用iServer/WEB-INF/lib下的所有jar包,否则运行时会出现一些奇怪的错误.这些jar包所在的位置为[iServer安装目录]\webapps\is

在线教学、视频会议软件 Webus Fox(2) 服务端开发手册

上次在<在线教学.视频会议软件 Webus Fox(1)文本.语音.视频聊天及电子白板基本用法>里介绍了软件的基本用法.本文主要介绍服务器端如何配置.开发. 1配置 1.1 IIS配置 Fox支持最基本的.net Framework4.0和IIS6/IIS7. 在IIS7中,对应应用程序池,需要配置为经典模式,支持.net4.0 1.2 web.config配置 Fox服务器端是暂时是host在IIS上,将来将Host在Windows Service上.对于IIS的配置,web.config是

不要害怕服务端开发(一)

经历了前端技术的变更,发现我再也不想去追前端技术了,同学们做java的现在还是做java,我之前做flash的现在没饭吃了,去年改做h5,行业不好混啊,java稳如狗. 之前一直想学服务端开发,现在改变了工作环境,稍微有点时间了,加上最近上班的工作实在是闲,上头布置的游戏前端demo,用3d写了一个,然后用2d写了一个,后面又用h5写了一个. 也找不到好的形容词去形容现在的状态,因为还在试用期,所以不敢太冒进,一直停留在写demo的进度上. 昨天开始,学着用nodejs写来静态服务器. 然后今天

专业移动APP 手机APP后台服务端开发

专业移动APP 手机APP后台服务端开发优势:8年PHP 9年JAVA开发经验采用THINKPHP 和JFINAL ,SBadmin 2框架进行开发完善的开发文档免费一年BUG维护 可签协议,可先预付20%,也可分段付款, 亲 请带好需求文档! QQ 184377367 报价原则1:功能列表2:兼容性3:安全要求等级4:是否需要完善的开发文档5:后台UI是否有要求

服务端开发之下载图片等文件

服务端开发中,当客户端需要加载服务端发送的图片文件时,需要服务端提供一个下载图片的程序. 下载图片工具类的代码如下: import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class DownloadImageUtil { public stat

Teamcenter9 ITK服务端开发配置

说明:由于Tc9创造性的出现了64bit的版本,还有个更创造性的要求,就是64位的只能在VS2008sp1中编译,故以VS2008重新写了开发配置 首选项名称 TC_customization_libraries 环境 首先,来个环境的截图,E盘下目录. 创建项目 Eg:libMax_register_callbacks.c #include <tc.h> #include <user_server_exits.h> #include <user_exits/user_exi

现代Java服务端开发核心技术栈

现代Java服务端开发核心技术栈 现代Java服务端开发核心技术 Java语言基础篇 互联网生态下的Java Java开发工具箱 Java基础项目搭建 Java数据存储 Java数据运算 Java程序流程控制 Java数组 Java面向对象程序设计 Java异常处理 Java枚举 Java注解 Java 泛型 Java集合 Java IO Java NIO Java操作数据库 Java日志 Java操作XML Java正则表达式 Java8新特性实战 Java9新特性实战 Java10新特性实战

现代Java服务端开发核心技术之开发工具箱

现代Java服务端开发核心技术之开发工具箱 现代Java服务端开发核心技术 2.1 开发工具概述 俗话说,工欲善其事必先利其器,掌握一些日常开中常用的工具软件能够大大提开发效率,工具本身的目的也是解放生产力.在安装各种软件时注意如果没有特殊需要不必使用最新版本,尤其是操作系统,例如当前(2018/10/12)最新版的macOS是10.14,但是运行在macOS之上的其他应用软件可能还没有及时做兼容新系统的版本,可能在系统升级之后无法正常使用,因此推荐在新系统正式推出半年后再升级最为稳妥. 而且软