WebAPI调用笔记
前言
即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录。
接口描述
首先说明一下,基于Http协议的Get、Post请求,网上有很多例子,其实方法都是一样的,这块没什么好说的,我也测试过我的代码,正常的调用是没问题的。问题不是在请求方法上,而是在如何取出数据上。
因为对应我需求接口的开发是个实习生,对于如何提供接口,他并不熟悉,其中我还帮了一些忙,但我毕竟不是很熟BS端那块,纠结了1天多才调通接口。
描述一下需求接口:
OA方需要提供头像下载的接口供即时通信初次登陆时下载头像。
思路如下:
1.接口内将OA头像文件夹打包压缩成zip文件
2.将zip文件,转成字节流byte[]
3.将字节流byte[]序列化成json格式 返回
问题
思路1、2他按照我的要求做了,我测试接口后遇到的问题是取出的byte[]顺序不对,我本地反序列化后转成zip文件没法解压,多次测试后我又让他按照思路3来做。
测试接口,遇到了个问题,我反序列化出来的json字符串,包含了d,如下:
解决
因为不熟悉这块,我不知道如何取出d中的数据,上网查也不好查,因为几乎没有问这块的,后来我问OA做手机端的同事,才知道他们提供的接口方法都是void,不在返回值的,都是通过http的Context.Response返回的,在调用的时候不会出现这个d字符。然而他也不知道如何取出d中的数据,没办法只能在网上再找,后来在一篇CSDN的博客中的评论里找到了办法,将json转换成hash表的格式,然后直接取出来d的数据,然后反序列化json成对象,如下:
string headjson = WebAPIHelper.HttpPost(OAAddress, null); JavaScriptSerializer json = new JavaScriptSerializer(); Hashtable table = (Hashtable)json.Deserialize(headjson, typeof(Hashtable)); string d = table["d"].ToString();//取出d中的数据 HeadByte h1 = JsonConvert.DeserializeObject<HeadByte>(d);
到此为止,问题终于解决。
拓展
因为比较在意普通的接口是如何提供返回结果的,我特意看一下OA中的WebAPI方法,同事说过,不带d的方法无需返回值,使用void即可,请求返回的结果都在Response中,json字符串出现d的原因是方法的返回值是string类型,特附上不需要另外取json中d的方法:
1 [ScriptMethod(ResponseFormat = ResponseFormat.Json)] 2 [WebMethod(Description = " 获取筛选条件", EnableSession = true)] 3 public void GetTestList(string Type,string UserID) 4 { 5 Context.Response.ContentType = "text/plain"; 6 Context.Response.Charset = "UTF-8"; 7 Context.Response.ContentEncoding = System.Text.Encoding.UTF8; 8 Dictionary<string, object> dicReturn = new Dictionary<string, object>(); 9 Dictionary<string, object> dic = new Dictionary<string, object>(); 10 string jsonStr = string.Empty;//保存返回的json字符串 11 try 12 { 13 //将返回的结果放到dic中 14 //可以放到list里也可以放到DataTable中 15 List<SortModel> list = xxx.GetXXXXXList(parames1, parames2); 16 dic.Add("Count", xxx.GetXXXXXCount(parames1, parames2)); 17 dic.Add("List", list); 18 //或者 19 DataTable dt = xxx.GetXXXXX(parames1, parames2); 20 dic.Add("Count", xxx.GetXXXXXCount(parames1, parames2); 21 dic.Add("List2", dt); 22 //获取成功 23 dicReturn.Add("State", "1"); 24 dicReturn.Add("ErrorMsg", "获取成功"); 25 dicReturn.Add("Data", dic); 26 dicReturn.Add("ErrCode", "0"); 27 } 28 catch (Exception ex) 29 { 30 dicReturn.Add("State", "0"); 31 dicReturn.Add("ErrorMsg", ex.Message); 32 dicReturn.Add("Data", ""); 33 dicReturn.Add("ErrCode", "1"); 34 } 35 //序列化 json字符串 36 jsonStr = JsonConvert.SerializeObject(dicReturn); 37 Context.Response.Write(jsonStr); 38 Context.Response.End(); 39 40 }
附上post请求的方法:
1 /// <summary> 2 /// 同步Post 3 /// </summary> 4 /// <param name="Url"></param> 5 /// <param name="postDataStr"></param> 6 /// <returns>返回json格式</returns> 7 public static string HttpPost(string Url, IDictionary<string, string> Params) 8 { 9 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url); 10 request.Method = "POST"; 11 //request.ContentType = "application/x-www-form-urlencoded"; 12 request.ContentType = "application/json"; 13 request.CookieContainer = cookie; 14 15 //发送POST数据 16 StringBuilder bufferParams = new StringBuilder(); 17 if (!(Params == null || Params.Count == 0)) 18 { 19 int i = 0; 20 foreach (string key in Params.Keys) 21 { 22 if (i > 0) 23 { 24 bufferParams.AppendFormat("&{0}={1}", key, Params[key]); 25 } 26 else 27 { 28 bufferParams.AppendFormat("{0}={1}", key, Params[key]); 29 i++; 30 } 31 } 32 } 33 request.ContentLength = Encoding.UTF8.GetByteCount(bufferParams.ToString()); 34 35 Stream myRequestStream = request.GetRequestStream(); 36 StreamWriter myStreamWriter = new StreamWriter(myRequestStream, Encoding.GetEncoding("gb2312")); 37 myStreamWriter.Write(bufferParams); 38 myStreamWriter.Close(); 39 40 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 41 42 response.Cookies = cookie.GetCookies(response.ResponseUri); 43 Stream myResponseStream = response.GetResponseStream(); 44 StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8")); 45 string retString = myStreamReader.ReadToEnd(); 46 #region ContentType = "application/x-www-form-urlencoded"处理方式 47 //服务器端返回的是一个XML格式的字符串,XML的Content才是我们所需要的数据 48 //XmlTextReader Reader = new XmlTextReader(myResponseStream); 49 //Reader.MoveToContent(); 50 //string retString = Reader.ReadInnerXml();//取出Content中的数据 51 //Reader.Close(); 52 #endregion 53 myStreamReader.Close(); 54 myResponseStream.Close(); 55 56 return retString; 57 }
总结
对于WebAPI调用,在BS端中,实在是很小白的东西,但是再怎么小白,没做过还是会花一些时间的。2019年,希望自己可以成长的更快,变得更优秀。
ASP.NET CORE 学习之自定义异常处理
为什么异常处理选择中间件?
传统的ASP.NET可以采用异常过滤器的方式处理异常,在ASP.NET CORE中,是以多个中间件连接而成的管道形式处理请求的,不过常用的五大过滤器得以保留,同样可以采用异常过滤器处理异常,但是异常过滤器不能处理MVC中间件以外的异常,为了全局统一考虑,采用中间件处理异常更为合适
为什么选择自定义异常中间件?
先来看看ASP.NET CORE 内置的三个异常处理中间件 DeveloperExceptionPageMiddleware , ExceptionHandlerMiddleware, StatusCodePagesMiddleware
1.DeveloperExceptionPageMiddleware
能给出详细的请求/返回/错误信息,因为包含敏感信息,所以仅适合开发环境
2.ExceptionHandlerMiddleware (蒋神博客 http://www.cnblogs.com/artech/p/error-handling-in-asp-net-core-3.html)
仅处理500错误
3.StatusCodePagesMiddleware (蒋神博客 http://www.cnblogs.com/artech/p/error-handling-in-asp-net-core-4.html)
能处理400-599之间的错误,但需要Response中不能包含内容(ContentLength=0 && ContentType=null,经实验不能响应mvc里未捕获异常)
由于ExceptionHandlerMiddleware和StatusCodePagesMiddleware的各自的限制条件,两者需要搭配使用。相比之下自定义中间件更加灵活,既能对各种错误状态进行统一处理,也能按照配置决定处理方式。
CustomExceptionMiddleWare
首先声明异常中间件的配置类
1 /// <summary> 2 /// 异常中间件配置对象 3 /// </summary> 4 public class CustomExceptionMiddleWareOption 5 { 6 public CustomExceptionMiddleWareOption( 7 CustomExceptionHandleType handleType = CustomExceptionHandleType.JsonHandle, 8 IList<PathString> jsonHandleUrlKeys = null, 9 string errorHandingPath = "") 10 { 11 HandleType = handleType; 12 JsonHandleUrlKeys = jsonHandleUrlKeys; 13 ErrorHandingPath = errorHandingPath; 14 } 15 16 /// <summary> 17 /// 异常处理方式 18 /// </summary> 19 public CustomExceptionHandleType HandleType { get; set; } 20 21 /// <summary> 22 /// Json处理方式的Url关键字 23 /// <para>仅HandleType=Both时生效</para> 24 /// </summary> 25 public IList<PathString> JsonHandleUrlKeys { get; set; } 26 27 /// <summary> 28 /// 错误跳转页面 29 /// </summary> 30 public PathString ErrorHandingPath { get; set; } 31 } 32 33 /// <summary> 34 /// 错误处理方式 35 /// </summary> 36 public enum CustomExceptionHandleType 37 { 38 JsonHandle = 0, //Json形式处理 39 PageHandle = 1, //跳转网页处理 40 Both = 2 //根据Url关键字自动处理 41 }
声明异常中间件的成员
/// <summary> /// 管道请求委托 /// </summary> private RequestDelegate _next; /// <summary> /// 配置对象 /// </summary> private CustomExceptionMiddleWareOption _option; /// <summary> /// 需要处理的状态码字典 /// </summary> private IDictionary<int, string> exceptionStatusCodeDic; public CustomExceptionMiddleWare(RequestDelegate next, CustomExceptionMiddleWareOption option) { _next = next; _option = option; exceptionStatusCodeDic = new Dictionary<int, string> { { 401, "未授权的请求" }, { 404, "找不到该页面" }, { 403, "访问被拒绝" }, { 500, "服务器发生意外的错误" } //其余状态自行扩展 }; }
异常中间件主要逻辑
1 public async Task Invoke(HttpContext context) 2 { 3 Exception exception = null; 4 try 5 { 6 await _next(context); //调用管道执行下一个中间件 7 } 8 catch (Exception ex) 9 { 10 context.Response.Clear(); 11 context.Response.StatusCode = 500; //发生未捕获的异常,手动设置状态码 12 exception = ex; 13 } 14 finally 15 { 16 if (exceptionStatusCodeDic.ContainsKey(context.Response.StatusCode) && 17 !context.Items.ContainsKey("ExceptionHandled")) //预处理标记 18 { 19 var errorMsg = string.Empty; 20 if (context.Response.StatusCode == 500 && exception != null) 21 { 22 errorMsg = $"{exceptionStatusCodeDic[context.Response.StatusCode]}\r\n{(exception.InnerException != null ? exception.InnerException.Message : exception.Message)}"; 23 } 24 else 25 { 26 errorMsg = exceptionStatusCodeDic[context.Response.StatusCode]; 27 } 28 exception = new Exception(errorMsg); 29 } 30 31 if (exception != null) 32 { 33 var handleType = _option.HandleType; 34 if (handleType == CustomExceptionHandleType.Both) //根据Url关键字决定异常处理方式 35 { 36 var requestPath = context.Request.Path; 37 handleType = _option.JsonHandleUrlKeys != null && _option.JsonHandleUrlKeys.Count( 38 k => context.Request.Path.StartsWithSegments(k, StringComparison.CurrentCultureIgnoreCase)) > 0 ? 39 CustomExceptionHandleType.JsonHandle : 40 CustomExceptionHandleType.PageHandle; 41 } 42 43 if (handleType == CustomExceptionHandleType.JsonHandle) 44 await JsonHandle(context, exception); 45 else 46 await PageHandle(context, exception, _option.ErrorHandingPath); 47 } 48 } 49 } 50 51 /// <summary> 52 /// 统一格式响应类 53 /// </summary> 54 /// <param name="ex"></param> 55 /// <returns></returns> 56 private ApiResponse GetApiResponse(Exception ex) 57 { 58 return new ApiResponse() { IsSuccess = false, Message = ex.Message }; 59 } 60 61 /// <summary> 62 /// 处理方式:返回Json格式 63 /// </summary> 64 /// <param name="context"></param> 65 /// <param name="ex"></param> 66 /// <returns></returns> 67 private async Task JsonHandle(HttpContext context, Exception ex) 68 { 69 var apiResponse = GetApiResponse(ex); 70 var serialzeStr = JsonConvert.SerializeObject(apiResponse); 71 context.Response.ContentType = "application/json"; 72 await context.Response.WriteAsync(serialzeStr, Encoding.UTF8); 73 } 74 75 /// <summary> 76 /// 处理方式:跳转网页 77 /// </summary> 78 /// <param name="context"></param> 79 /// <param name="ex"></param> 80 /// <param name="path"></param> 81 /// <returns></returns> 82 private async Task PageHandle(HttpContext context, Exception ex, PathString path) 83 { 84 context.Items.Add("Exception", ex); 85 var originPath = context.Request.Path; 86 context.Request.Path = path; //设置请求页面为错误跳转页面 87 try 88 { 89 await _next(context); 90 } 91 catch { } 92 finally 93 { 94 context.Request.Path = originPath; //恢复原始请求页面 95 } 96 }
使用扩展类进行中间件注册
1 public static class CustomExceptionMiddleWareExtensions 2 { 3 4 public static IApplicationBuilder UseCustomException(this IApplicationBuilder app, CustomExceptionMiddleWareOption option) 5 { 6 return app.UseMiddleware<CustomExceptionMiddleWare>(option); 7 } 8 }
在Startup.cs的Configuref方法中注册异常中间件
1 app.UseCustomException(new CustomExceptionMiddleWareOption( 2 handleType: CustomExceptionHandleType.Both, //根据url关键字决定处理方式 3 jsonHandleUrlKeys: new PathString[] { "/api" }, 4 errorHandingPath: "/home/error"));
接下来我们来进行测试,首先模拟一个将会进行页面跳转的未经捕获的异常
访问/home/about的结果
访问/home/test的结果 (该地址不存在)
OK异常跳转页面的方式测试完成,接下来我们测试返回统一格式(json)的异常处理,同样先模拟一个未经捕获的异常
访问/api/token/gettesterror的结果
访问/api/token/test的结果 (该地址不存在)
访问/api/token/getvalue的结果 (该接口需要身份验证)
测试完成,页面跳转和统一格式返回都没有问题,自定义异常中间件已按预期工作
需要注意的是,自定义中间件会响应每个HTTP请求,所以处理逻辑一定要精简,防止发生不必要的性能问题
MySQL数据库查询优化建议
1.
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
1 select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
1 select id from t where num=0
3.
应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
4.
应尽量避免在 where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
1 select id from t where num=10 or num=20
可以这样查询:
1 select id from t where num=10 2 union all 3 select id from t where num=20
5.
in 和 not in 也要慎用,否则会导致全表扫描,如:
1 select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
1 select id from t where num between 1 and 3
6.
下面的查询也将导致全表扫描:
1 select id from t where name like '李%'
若要提高效率,可以考虑全文检索。
7.
如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
1 select id from t where num=@num
可以改为强制查询使用索引:
1 select id from t with(index(索引名)) where num=@num
8.
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
1 select id from t where num/2=100
应改为:
1 select id from t where num=100*2
9.
应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
1 select id from t where substring(name,1,3)='abc'
name以abc开头的id,应改为:
1 select id from t where name like 'abc%'
10.
不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.
在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12.
不要写一些没有意义的查询,如需要生成一个空表结构:
1 select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
1 create table #t(...)
13.
很多时候用 exists 代替 in 是一个好的选择:
1 select num from a where num in(select num from b)
用下面的语句替换:
1 select num from a where exists(select 1 from b where num=a.num)
14.
并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15.
索引并不是越多越好,索引固然可 以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
16.
应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17.
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18.
尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19.
任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20.
尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21.
避免频繁创建和删除临时表,以减少系统表资源的消耗。
22.
临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
23.
在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.
如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.
尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.
使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.
与临时表一样,游标并不是不可使 用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.
在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC 消息。
29.
尽量避免大事务操作,提高系统并发能力。
30.
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
.NET操作XML文件之泛型集合的序列化与反序列化
程序集:System.Xml.Serialization(在 System.Xml.Serialization.dll 中)
命名空间:System.Xml.Serialization
公共函数代码:
1 /// <summary> 2 ///序列化 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 /// <param name="serialObject"></param> 6 /// <returns></returns> 7 public static string XmlSerializer<T>(T serialObject) where T : class 8 { 9 var emptyNamepsaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }); 10 var serializer = new XmlSerializer(serialObject.GetType()); 11 var settings = new XmlWriterSettings(); 12 settings.OmitXmlDeclaration = true; 13 settings.Encoding = Encoding.UTF8; 14 using (var stream = new StringWriter()) 15 using (var writer = XmlWriter.Create(stream, settings)) 16 { 17 serializer.Serialize(writer, serialObject, emptyNamepsaces); 18 return stream.ToString(); 19 } 20 } 21 22 /// <summary> 23 ///反序列化 24 /// </summary> 25 /// <typeparam name="T"></typeparam> 26 /// <param name="xml"></param> 27 /// <returns></returns> 28 public static T DeserializeObject<T>(string xml) where T : class 29 { 30 using (var str = new StringReader(xml)) 31 { 32 var xmlSerializer = new XmlSerializer(typeof(T)); 33 var result = (T)xmlSerializer.Deserialize(str); 34 return result; 35 } 36 }
- XmlSerializerNamespaces 包含的 XML 命名空间必须符合称为的 www.w3.org 规范,XML 命名空间,而XmlQualifiedName.Empty表示无XML限定名。
- XmlSerializer(Type):初始化 XmlSerializer 类的新实例,该类可以将指定类型的对象序列化为 XML 文档,也可以将 XML 文档反序列化为指定类型的对象。(应用程序通常定义若干类,XmlSerializer 将这些类转换为单个 XML 实例文档。 但是,XmlSerializer 只需知道一种类型,即表示 XML 根元素的类的类型。 XmlSerializer 自动序列化所有从属类的实例。 同样,反序列化仅需要 XML 根元素的类型)。
- XmlWriterSettings:使用XmlWriterSettings实例对象进行生成的XML的设置。如是否缩进文本、缩进量、每个节点一行等配置(另:XmlReaderSettings代替XmlValidatingReader可用于XML验证)。
性能建议:
为了提高性能,XML 序列化基础结构动态生成程序集,以便对指定类型进行序列化和反序列化。 该基础结构将找到并重新使用这些程序集。 仅当使用以下构造函数时,才会发生此行为:
- XmlSerializer(Type)
- XmlSerializer.XmlSerializer(Type, String)
如果使用任何其他构造函数,则将生成同一个程序集的多个版本,这些版本始终不予卸载,从而导致内存泄漏和性能低下。 最简单的解决方案是使用前面提到的两个构造函数中的一个。
举例说明:
新建一个订单类一个订单明细类,如图:
实例化并添加属性值:
1 List<OrderDetail> orderDetail = new List<OrderDetail> { 2 3 new OrderDetail{ ID=100, ItemNum=3, ItemPrice=199,OrderDetailID="L001001", OrderDetailItem="电视机", OrderDetailMark="挂账", OrderID="L001" }, 4 5 new OrderDetail{ ID=101, ItemNum=1, ItemPrice=1,OrderDetailID="L001002", OrderDetailItem="肥皂", OrderDetailMark="挂账", OrderID="L001" }, 6 7 new OrderDetail{ ID=102, ItemNum=2, ItemPrice=0.05M,OrderDetailID="L001003", OrderDetailItem="方便袋", OrderDetailMark="挂账", OrderID="L001" } 8 9 }; 10 11 Order order = new Order { ID = 1, OrderDate = DateTime.Now, OrderID = "L001", OrderTax = true, OrderTotal = 200.05M, orderDetails = orderDetail };
然后调用序列化方法如下:
1 //序列化 2 3 string xml = XMLSerializer.XmlSerializer<Order>(order); 4 5 Console.WriteLine("-------------------------------------------"); 6 7 Console.WriteLine(xml); 8 9 Console.WriteLine("-------------------------------------------");
结果如图:
继续进行反序列化,代码如:
//反序列化 Order dOrder = XMLSerializer.DeserializeObject<Order>(xml); Console.WriteLine("订单有明细:"+dOrder.orderDetails.Count+"项,共计:"+dOrder.OrderTotal+"元"); Console.WriteLine("编号\t名称\t单价"); foreach (var item in dOrder.orderDetails) { Console.WriteLine(item.OrderDetailID + "\t" + item.OrderDetailItem + "\t" + item.ItemPrice); }
运行结果如:
不过请注意,XmlSerializer 无法序列化或反序列化以下数组:
- ArrayList 的数组
- List<T> 的数组
- 业务需要不用序列化的属性,只需给该属性加上XmlIgnore
特性即可,再次执行序列化操作如图,大家会发现已经少了一个子节点。
序列化后的字符串可参与传输,复杂类型存储,SQL查询等业务。
本文示例代码点击下载。
本文属于转载文章,点击这里查看原文
Asp.Net Core 轻松学-多线程之Task快速上手
前言
Task是从 .NET Framework 4 开始引入的一项基于队列的异步任务(TAP)模式,从 .NET Framework 4.5 开始,任何使用 async/await 进行修饰的方法,都会被认为是一个异步方法;实际上,这些异步方法都是基于队列的线程任务,从你开始使用 Task 去运行一段代码的时候,实际上就相当于开启了一个线程,默认情况下,这个线程数由线程池 ThreadPool 进行管理的。
1. Task 的使用方法
Task 的使用用方法非常简单,一行代码就可以开始一个异步任务
1.1 最简单的使用方式
static void EasyTask()
{
// 执行一个无返回值的任务
Task.Run(() =>
{
Console.WriteLine("runing...");
});
// 执行一个返回 int 类型结果的任务
Task.Run<int>(() =>
{
return new Random().Next();
});
// 声明一个任务,仅声明,不执行
Task t = new Task(() =>
{
Console.WriteLine("");
});
}
上面的代码看起来非常简单,只需要一行代码就完成了一个异步任务线程,先不要去深究其背后的原理,对于新手来说,先解决能用,再去了解为什么可以这样使用,不然,一开始就失去了学习的信心
2.1 使用 TaskFactory 工厂开始异步任务
static void Factory()
{
List<Task<int>> tasks = new List<Task<int>>();
TaskFactory factory = new TaskFactory();
tasks.Add(factory.StartNew<int>(() =>
{
return 1;
}));
tasks.Add(factory.StartNew<int>(() =>
{
return 2;
}));
foreach (var t in tasks)
{
Console.WriteLine("Task:{0}", t.Result);
}
}
上面的代码使用 TaskFactory 创建并运行了两个异步任务,同时把这两个任务加入了任务列表 tasks 中,然后立即迭代此 tasks 获取异步任务的执行结果,使用 TaskFactory 工厂类,可以创建一组人物,然后依次执行它们
2.3 执行上面的代码,输出结果如下
3. 处理 Task 中的异常
异步任务中发生异常会导致任务抛出 TaskCancelException 的异常,仅表示任务退出,程序应当捕获该异常;然后,立即调用 Task 进行状态判断,获取内部异常
3.1 模拟抛出异常
static void SimpleTask()
{
var task = Task.Run(() =>
{
Console.WriteLine("SimpleTask");
Task.Delay(1000).Wait();
throw new Exception("SimpleTask Error");
});
try
{
task.Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
if (task.IsCompletedSuccessfully)
{
Console.WriteLine("IsCompleted");
}
}
上面的代码模拟了 Task 内部发生的异常,并捕获了异常,通常情况下,推荐使用 Task 的任务状态判断以进行下一步的任务处理(如果需要),如果仅仅是简单的执行一个异步任务,直接捕获异常即可,这里使用了状态判断,如果任务已完成,则打印一则消息:IsCompleted;很明显,在上面的代码中,此 “IsCompleted” 消息并不会被打印到控制台
注意,这里使用了 task.IsCompletedSuccessfully 而不是 task.IsCompleted,这两者的区别在于,前者只有在任务正常执行完成,无异常,无中途退出指令的情况下才会表示已完成,而 task.IsCompleted 则仅仅表示“任务完成”
3.2 执行程序,输出结果
4. 同步上下文
在 WinForm/WPF 应用程序中,也常常需要在 UI 上开辟异步任务,通常情况下,窗体控件仅允许创建其的线程访问,在没有 Task 的时代,处理异步上下文到同步上下文是一件非常复杂的事情,在 Task 出现以后,提供了 TaskScheduler 任务调度器,让我们可以非常方便的在异步线程中访问 UI 线程的资源
4.1 获取当前线程上下文对象
static void TaskSynchronizationContext()
{
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
var t1 = Task.Factory.StartNew<int>(() =>
{
return 1;
});
t1.ContinueWith((atnt) =>
{
// 从这里访问 UI 线程的资源
Console.WriteLine("从这里访问 UI 线程的资源");
}, UISyncContext);
}
从上面的代码可以发现,仅仅需要调用 TaskScheduler.FromCurrentSynchronizationContext() 获得当前线程的同步上下文,然后在执行异步任务的时候传入,即可访问当前线程创建的 UI 资源
5. Task 的运行方式
5.1 基于 ThreadPool 线程池的方式
一个异步任务总是处于队列中,任务队列基于先进先出的原则,最新进入队列的任务总是最先被执行;但是,在多线程的环境下,最先执行并不意味着最先结束,意识到这一点很重要,每个任务可调度的资源和处理的进度决定了任务的完成时间。
默认情况下,所有的任务都使用 ThreadPool 的资源,当你开启一个 Task 的时候,实际上,是由 ThreadPool 分配了一个线程,ThreadPool 的上限取决于很多方面的因素,例如虚拟内存的大小,当 Task 开启的数量超过ThreadPool 的上限的时候,Task 将进入排队状态,可以手动设置 ThreadPool 的大小
static void SetThreadPool()
{
var available = ThreadPool.SetMaxThreads(8, 16);
Console.WriteLine("Result:{0}", available);
}
上面的代码表示设置当前程序可使用的线程池大小,但是,SetMaxThreads 的值不应该小于托管服务器的 CPU 核心数量,否则,变量 available 的值将显示为 false,表示未成功设置线程池上限
注意:ThreadPool 上的所有线程都是后台线程,也就是说,其IsBackground属性是true,在托管程序退出后,ThreadPool 也将会退出。
5.2 长时间运行于后台的任务
在创建 Task 的时候,我们可能需要做一些长时间运行的业务,这个时候如果使用默认的 ThreadPool 资源,在并发状态下,这是不合适的,因为该任务总是长时间的占用线程池中的资源,导致线程池数量受限,这种情况下,可以在创建任务的时候使用指定 TaskCreationOptions.LongRunning 方式创建 Task
static void LongTask()
{
Task.Factory.StartNew(() =>
{
Console.WriteLine("LongRunning Task");
}, TaskCreationOptions.LongRunning);
}
上面的代码看起来和创建普通的 Task 任务并没有多大的区别,唯一不同的是,在参数中传入了 TaskCreationOptions.LongRunning,指定这个是一个 LongRunning 类型的任务,当TaskFactory 收到这样一个类型的任务时,将会为这个任务开辟一个独立的线程,而不是从 ThreadPool 中创建
6. 有条件的 Task
Task 内部提供多种多样的基于队列的链式任务管理方法,通过使用这些快捷方式,可以让异步队列有序的执行,比如ContinueWith(),ContinueWhenAll(),ContinueWhenAny(),WaitAll(),WaitAny(),WhenAll(),WhenAny()
6.1 使用演示
static void WithTask()
{
var order1 = Task.Run(() =>
{
Console.WriteLine("Order 1");
});
// 匿名委托将等待 order1 执行完成后执行,并将 order1 对象作为参数传入
order1.ContinueWith((task) =>
{
Console.WriteLine("Order 1 Is Completed");
});
var t1 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t1"); });
var t2 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t2"); });
var t3 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t3"); });
Task.WaitAll(t1, t2, t3);
// t1,t2,t3 完成后输出下面的消息
Console.WriteLine("t1,t2,t3 Is Complete");
var t4 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t4"); });
var t5 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t5"); });
var t6 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t6"); });
Task.WaitAny(t4, t5, t6);
// 当任意任务完成时,输出下面的消息,目前按延迟时间计算,在 t4 完成后立即输出下面的信息
Console.WriteLine("t4,t5,t6 Is Complete");
var t7 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t7"); });
var t8 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t8"); });
var t9 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t9"); });
var whenAll = Task.WhenAll(t7, t8, t9);
// WhenAll 不会等待,所以这里必须显示指定等待
whenAll.Wait();
// 当所有任务完成时,输出下面的消息
Console.WriteLine("t7,t8,t9 Is Complete");
var t10 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t10"); });
var t11 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t11"); });
var t12 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t12"); });
var whenAny = Task.WhenAll(t10, t11, t12);
// whenAny 不会等待,所以这里必须显示指定等待
whenAny.Wait();
// 当任意任务完成时,输出下面的消息,目前按延迟时间计算,在 t10 完成后立即输出下面的信息
Console.WriteLine("t10,t11,t12 Is Complete");
}
6.2 执行上面的代码,输出结果如下
值得注意的是,当调用 WhenAll 方法时,会返回执行任务的状态,此状态是所有任务的统一状态,如果执行了 3 个任务,而其中一个出错,则返回任务状态表示为:Faulted,如果任意任务被取消,则状态为:Canceled;
当调用 WhenAny() 方法时,表示任意任务完成即可表示完成,此时,会返回最先完成的任务信息
注意:WhenAll 和 WhenAny 方法正常执行,无异常,无取消,则所返回的完成状态表示为:RanToCompletion
结束语
- 本章简要介绍了基于队列的异步任务(TAP)使用方式
- 介绍了TAP 运行的方式、以及异常处理
- 同时还介绍了如何使用 UI 线程同步上下文对象,以及有条件使用 TAP 的各种方法
示例代码下载
https://files.cnblogs.com/files/viter/Ron.TaskDemo.zip
Asp.Net Core 轻松学-多线程之Task(补充)
前言
在上一章 Asp.Net Core 轻松学-多线程之Task快速上手 文章中,介绍了使用Task的各种常用场景,但是感觉有部分内容还没有完善,在这里补充一下。
1. 任务的等待
在使用 Task 进行基于队列的异步任务(TAP)的时候,对于刚入门的同学来说,只是简单的了解了使用 Task 可以在后台处理异步任务,但是对于阻塞调用可能还有有一些不太明白,异步任务默认是不阻塞的执行过程,当一个 Task 被创建出来的时候,并没有被压入队列中,而是开始执行的时候,才会进入队列中;执行一个异步任务可以设置等待
1.1 使用 await 进行等待任务完成
[HttpGet]
public async Task<string> Get()
{
string result = string.Empty;
await Task.Run(() =>
{
result = "Hello World!";
});
return result;
}
上面的异步方法 Get() 内部执行了一个 Task,为了保持方法在返回的时候能够给变量 result 设置值,这里使用了 await 等待任务完成,在任务未完成前,即 result 未被设置值 Hello World! 前,该接口将一直阻塞,直到任务完成。
1.1 使用 Wait()
[HttpGet("WaitTest")]
public string WaitTest(int id)
{
string result = string.Empty;
var waitTask = Task.Run(() =>
{
result = "Hello World!";
});
waitTask.Wait(TimeSpan.FromSeconds(5));
return result;
}
上面的代码定义了一个 TAP ,waitTask 使用了 Wait() 方法进行等待并传入一个时间,表示等待 5 秒中后退出阻塞过程,
1.2 使用 CancellationToken 方法
[HttpGet("WaitToken")]
public string WaitToken(int id)
{
var result = string.Empty;
CancellationTokenSource cts = new CancellationTokenSource();
var taskToken = Task.Run(() =>
{
cts.CancelAfter(TimeSpan.FromSeconds(1));
Task.Delay(2000).Wait();
result = "Hello World!";
});
taskToken.Wait(cts.Token);
return result;
}
上面的任务 taskToken ,则使用了取消令牌,当令牌没有收到取消通知的时候,该任务将一直等待, taskToken 任务内部指示取消令牌 1 秒后取消,同时,任务内部使用 Task.Delay 阻塞 2 秒,这很特别,这种设置使得 taskToken 任务将引发任务取消的异常而导致无法给 result 变量进行值设置,如果你对取消令牌不太了解,建议阅读我之前的文章 Asp.Net Core 轻松学-多线程之取消令牌
2. 同步方法中的异步任务
在同步方法中,我们可以非常容易的创建一个 Task 任务,特别是 .Net Core 提供了 Task 这么方便的使用方式的情况下,在某些场景下,就会出现一些意想不到的问题,我的忠告是:在使用 TAP 的时候,尽可能的使用可等待的任务,特别是需要注意不要在 TAP 中运行一些耗时较长的任务,比如批量处理数据、事务过程等等,如果真的是需要有这一类型的任务需要使用 Task 进行处理,那么我的建议是,将这些任务缩小,然后尽快的结束,比如,你可以使用消息队列来执行批量处理数据,而 Task 的任务则缩小至把处理条件丢到消息队列中,这样的解耦才是正确的选择,长时间运行于后台的 TAP,常常导致许多问题,比如消息冒泡、服务重启,都有可能会中断 TAP 线程,要知道,所有的 TAP 都是后台线程,当主进程退出的时候,后台线程也将被清理
3. 手动排队任务
在 TheadPool 内部,提供了一个排队的方法,当线程池资源可用后,将会自动的执行该队列,这样做的好处显而易见,就是你可以通过定义一系列的任务,然后等待线程池去按顺序处理它,这个排队的过程本质上就是队列
3.1 手动排队任务
[HttpGet("TaskQueue")]
public bool TaskQueue()
{
var inQueues = ThreadPool.QueueUserWorkItem(ThreadProc);
return inQueues;
}
private void ThreadProc(Object stateInfo)
{
Console.WriteLine("此任务来自线程池队列执行");
}
上面的代码中,在 TaskQueue() 内部使用 ThreadPool.QueueUserWorkItem() 将方法 ThreadProc(Object stateInfo) 压入队列中,并返回一个值:inQueues ,该值指示任务压入队列是否成功,在 .Net Core 中,ThreadPool.QueueUserWorkItem() 提供了 3 个方法重载,可以按需使用;使用重载方法,甚至可以在压入任务的时候传入参数调用,类似下面的代码
3.2 在排队任务时传递参数
[HttpGet("TaskQueue")]
public bool TaskQueue()
{
var inQueues = ThreadPool.QueueUserWorkItem(ThreadProc);
var inQueuesSecond = ThreadPool.QueueUserWorkItem(ThreadProc, "这是一条测试消息");
return inQueues;
}
private void ThreadProc(Object stateInfo)
{
Console.WriteLine(stateInfo);
}
如果业务需要,该参数还可传入实体对象
4. 混合方法(Hybrid Approach)
使用混合方法执行 TAP 的好处是,可以隐藏业务实现细节,提供统一调用入口
4.1 定义混合方法
[HttpGet("HyBrid")]
public Task<string> HyBrid([FromQuery]string value)
{
return HyBridInternal(value);
}
private async Task<string> HyBridInternal(string value)
{
await Task.Run(() =>
{
Console.WriteLine(value);
});
return "Your Input:" + value;
}
混合方法,不要被它的名头唬住,你可以把它看成一个方法重载,这样做的好处是,当发生异常是,你可以快速的定位到出现异常的方法,而不是任务
结束语
本文的内容只是上一篇文章的补充,所以这里就不在放入执行结果,但是示例代码还是一样的奉上