LINQ基础(三)

一.并行LINQ
  System.Linq名称空间中包含的类ParallelEnumerable可以分解查询的工作,使其分布在多个线程上。
  尽管Enumerable类给IEnumerable<T>接口定义了扩展方法,但ParallelEnumerable类的大多数扩展方法是ParallerQuery<TSource>类的扩展。例如,AsParallel()方法,它扩展了IEnumerable<T>接口,返回ParallelQuery<T>类,所以正常的集合类可以以平行方式查询。

  1.并行查询
  下面演示并行LINQ(Parallel LINQ,PLINQ):

//用随机值填充一个大型的int集合
        // Enumerable.Range(0, arraySize),生成指定范围内的整数的空序列。
        //Select(x => r.Next(140)),用小于140的数填充集合
        static IEnumerable<int> SampleData()
        {
          const int arraySize = 100000000;
          var r = new Random();
          return Enumerable.Range(0, arraySize).Select(x => r.Next(140)).ToList();
        }

        static void IntroParallel()
        {
          var data = SampleData();

          var watch = new Stopwatch();

        //非并行LINQ
          watch.Start();
          var q1 = (from x in data
                    where Math.Log(x) < 4
                    select x).Average();
          watch.Stop();
          Console.WriteLine("sync {0}, result: {1}", watch.ElapsedMilliseconds, q1);

          watch.Reset();

          //使用data.AsParallel()进行并行LINQ
          watch.Start();
          var q2 = (from x in data.AsParallel()
                where Math.Log(x) < 4
                select x).Average();
          watch.Stop();
          Console.WriteLine("async {0}, result: {1}", watch.ElapsedMilliseconds, q2);
        }

  输出;
    
  发现并行查询时间用的少,在并行查询时CPU利用率达到100%

  与LINQ基础(二)(http://www.cnblogs.com/afei-24/p/6845551.html)中的LINQ查询一样,编译器会修改语法,以调用AsParallel,Where(),Select(),Average()方法:
  var q2 = data.AsParallel().Where(x => Math.Log(x)<4).Select(x => x).Average();
  AsParallel()方法用ParallerEnumerable类定义,以扩展IEnumerable<T>接口,所以可以对简单的数组调用它。AsParallel()方法返回ParallerQuery<T>。因为返回的类型,所以编译器选择的Where()方法是ParallerEnumerable.Where(),而不是Enumerable.Where()。
  对于PrarllelEnumerable类,查询是分区的,以便多个线程可以同时处理该查询。集合可以分为多个部分,其中每个部分由不同的线程处理。完成分区的工作后,就需要合并,获得所有部分的总和。

  2.分区器
  AsParallel()方法不仅扩展了IEnumerable<T>接口,还扩展了Partitioner类。通过它可以影响要创建的分区。
  Partitioner类用System,Collection.Concurrent名称空间定义,并且有不同的变体。Create()方法接受实现了IList<T>类的数组或对象,以及Boolean类型的参数,返回一个不同的Partitioner类型。Create()方法有多个重载版本。
    var q2 = (from x in Partitioner.Create(data).AsParallel()
      where Math.Log(x) < 4
        select x).Average();

  也可以对AsParallel()方法接着调用WithExecutionMode()和WithDegreeOfParallelism()方法,来影响并行机制。WithExecutionMode()方法可以传递ParallelExecutionMode的一个Default值或者ForceParallelism值。默认情况下,并行LINQ避免使用系统开销很高的并行机制。WithDegreeOfParallelism()方法,可以传递一个整数值,以指定应并行运行的最大任务数。如果查询不应使用全部CPU,这个方法很有用。

  3.取消
  要取消长时间运行的查询,可以给查询添加WithCancellation()方法,并传递一个CancellationToken令牌作为参数。CancellationToken令牌从CancellationTokenSource类中创建。
  举个例子,下面的查询在单独的线程中运行,如果取消了查询,在该线程中捕获一个OperationCanceledException类型的异常。在主线程中,可以调用CancellationTokenSource类的Cancle()方法取消任务。

var data = SampleData();
          var watch = new Stopwatch();

          watch.Start();
          Console.WriteLine("filled array");
          var sum1 = (from x in data
                      where Math.Log(x) < 4
                      select x).Average();
          Console.WriteLine("sync result {0}", sum1);

          var cts = new CancellationTokenSource();

          Task.Factory.StartNew(() =>
            {
              try
              {
                var res = (from x in data.AsParallel().WithCancellation(cts.Token)
                           where Math.Log(x) < 4
                           select x).Average();
                Console.WriteLine("query finished, result: {0}", res);
              }
              catch (OperationCanceledException ex)
              {
                Console.WriteLine(ex.Message);
              }
            });
          watch.Stop();
          Console.WriteLine("async {0}, result: {1}", watch.ElapsedMilliseconds, "res");
          Console.WriteLine("query started");
          Console.Write("cancel? ");
          string input = Console.ReadLine();
          if (input.ToLower().Equals("y"))
          {
              cts.Cancel();
              Console.WriteLine("sent a cancel");
          }

          Console.WriteLine("press return to exit");
          Console.ReadLine();

  二.表达式树
  在LINQ To Object 中,扩展方法需要将一个委托类型作为参数,这样就可以将lambda表达式赋予参数。lambda表达式也可以赋予Expression<T>类型的参数,C#编译器根据类型给lambda表达式定义不同的行为。如果类型是Expression<T>,编译器就从lambda表达式中创建一个表达式树,并存储在程序集中。这样就可以在运行期间分析表达式树,并进行优化,以便查询数据源。
  var racers = from r in Formula1.GetChampions()
    where r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria")
      select r;

  这个查询表达式使用了扩展方法Where(),Select()方法。Enumerable类定义了Where()方法,并将委托类型Func<T,bool>作为参数谓词:
  public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source,Func<TSource,bool> predicate);
  这样,就可以把lambda表达式赋予委托predicate。

  除了使用委托之外,编译器还会把表达式树放在程序集中。表达式树可以在运行期间读取。表达式树从派生自抽象基类Expression的类中构建。Expression和Expression<T>不同。继承自Expression类的表达式类有BinaryExpression,ConstantExpression,InvocationExpression等。编译器会从lambda表达式中创建表达式树。
  例如,lambda表达式r.Country == "Brazil"使用了ParameterExpression,MemberExpression,ConstantExpression,MethodCallExpression,来创建一个表达式树,并将该树存储在程序集中,之后在运行期间使用这个树,创建一个用于底层数据源的优化查询:

//DisplayTree方法在控制台上图形化的显示表达式树。其中传递一个Expression对象,并根据表达式的类型,把表达式的一些信息写到控制台上
                private static void DisplayTree(int indent, string message, Expression expression)
                {
                    string output = String.Format("{0} {1} ! NodeType: {2}; Expr: {3} ",
                          "".PadLeft(indent, ‘>‘), message, expression.NodeType, expression);

                    indent++;
                    switch (expression.NodeType)
                    {
                        case ExpressionType.Lambda:
                            Console.WriteLine(output);
                            LambdaExpression lambdaExpr = (LambdaExpression)expression;
                            foreach (var parameter in lambdaExpr.Parameters)
                            {
                                DisplayTree(indent, "Parameter", parameter);
                            }
                            DisplayTree(indent, "Body", lambdaExpr.Body);
                            break;
                        case ExpressionType.Constant:
                            ConstantExpression constExpr = (ConstantExpression)expression;
                            Console.WriteLine("{0} Const Value: {1}", output, constExpr.Value);
                            break;
                        case ExpressionType.Parameter:
                            ParameterExpression paramExpr = (ParameterExpression)expression;
                            Console.WriteLine("{0} Param Type: {1}", output, paramExpr.Type.Name);
                            break;
                        case ExpressionType.Equal:
                        case ExpressionType.AndAlso:
                        case ExpressionType.GreaterThan:
                            BinaryExpression binExpr = (BinaryExpression)expression;
                            if (binExpr.Method != null)
                            {
                                Console.WriteLine("{0} Method: {1}", output, binExpr.Method.Name);
                            }
                            else
                            {
                                Console.WriteLine(output);
                            }
                            DisplayTree(indent, "Left", binExpr.Left);
                            DisplayTree(indent, "Right", binExpr.Right);
                            break;
                        case ExpressionType.MemberAccess:
                            MemberExpression memberExpr = (MemberExpression)expression;
                            Console.WriteLine("{0} Member Name: {1}, Type: {2}", output,
                               memberExpr.Member.Name, memberExpr.Type.Name);
                            DisplayTree(indent, "Member Expr", memberExpr.Expression);
                            break;
                        default:
                            Console.WriteLine();
                            Console.WriteLine("{0} {1}", expression.NodeType, expression.Type.Name);
                            break;
                    }
                }

                static void Main()
                {
                    Expression<Func<Racer, bool>> expression = r => r.Country == "Brazil" && r.Wins > 6;

                    DisplayTree(0, "Lambda", expression);

                }

  输出:
  

  使用Expression<T>类型的一个例子是ADO.NET EF 和WCF数据服务的客户端提供程序。这些技术用Expression<T>参数定义了扩展方法。这样,访问数据库的LINQ提供程序就可以读取表达式,创建一个运行期间优化的查询,从数据库中获取数据。
  后面会单独介绍表达式树的使用。

  三.LINQ提供程序
  .NET包含几个LINQ提供程序。LINQ提供程序为特定的数据源实现了标准的查询操作符。LINQ提供程序也许会实现比LINQ定义的更多扩展方法,但至少要实现标准操作符。LINQ To XML实现了一些专门用于XML的方法,后面会详细介绍。
  LINQ提供程序的实现方案是根据名称空间和第一个参数的类型来选择的。实现扩展方法的类的名称空间必须是开放的,否则扩展方法就不在作用域内。在LINQ to Objects中定义的Where()方法的参数和LINQ To Entities中定义的Where()方法的参数不同:
    LINQ to Objects中定义的Where()方法:
      public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source,Func<TSource,bool> predicate);
    LINQ To Entities中定义的Where()方法:
      public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,Expression<Func<TSource,bool>> predicate);
  这两个类都在System.Linq的Syste,.Core程序集中实现。无论是用 Func<TSource,bool>传递参数,还是用Expression<Func<TSource,bool>>参数传递,lambda表达式都相同。只是编译器的行为不同,它根据source参数来选择。编译器根据其参数选择最匹配的方法。在ADO.NET EF中定义的ObjectContext类CreateQuery<T>()方法返回一个实现了IQueryable<TSource>接口的ObjectQuery<T>对象,因此EF使用Querable类的Where()方法。

时间: 2024-10-25 15:29:26

LINQ基础(三)的相关文章

20.C#LINQ基础和简单使用(十一章11.1-11.2)

终于看到了第11章,之前虽然也有看过,但没有太仔细,在工作中也偶尔会使用,但不明白其中的原理,那现在就来讲讲LINQ,做一做书虫~~ 首先先了解下LINQ的三个要点: LINQ不能把非常复杂的查询表达式转换成一行代码 使用LINQ不意味着你从此不再需要使用SQL LINQ不可能魔法般地让你成为架构天才 序列是LINQ的基础,在你看到一个查询表达式的时候,应该要想到它所涉及的序列:一开始总是存在至少一个序列,且通常在中间过程会转换成其他序列,也可能和其他序列连接在一起. 1 class Car 2

LINQ基础(二)

本文主要介绍LINQ查询操作符 LINQ查询为最常用的操作符定义了一个声明语法.还有许多查询操作符可用于Enumerable类. 下面的例子需要用到LINQ基础(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的一些代码 1.筛选 LINQ查询使用where子句添加条件表达式来筛选,where子句可以合并多个表达式. var racers = from r in Formula1.GetChampions() where r.Wins>15 &

Python全栈开发【基础三】

Python全栈开发[基础三]  本节内容: 函数(全局与局部变量) 递归 函数 一.定义和使用 函数最重要的是减少代码的重用性和增强代码可读性 1 def 函数名(参数): 2 3 ... 4 函数体 5 ... 6 返回值 函数的定义主要有如下要点: def:表示函数的关键字 函数名:函数的名称,日后根据函数名调用函数 函数体:函数中进行一系列的逻辑计算 参数:为函数体提供数据 返回值:当函数执行完毕后,可以给调用者返回数据. 总结使用函数的好处: 1.减少代码重用 2.保持一致性,易维护

JS基础三

1.delete删除对对象的属性和方法的定义.强制解除对它的引用,将其设置为 undefined delete 运算符不能删除开发者未定义的属性和方法. 2.void 运算符对任何值返回 undefined.该运算符通常用于避免输出不应该输出的值,没有返回值的函数真正返回的都是 undefined. 3.前增量运算符,就是数值上加 1,形式是在变量前放两个加号(++): var iNum = 10; ++iNum; 第二行代码把 iNum 增加到了 11,它实质上等价于: var iNum =

LINQ:开始使用 LINQ(三)- 使用 LINQ 进行数据转换

开始使用 LINQ(三)- 使用 LINQ 进行数据转换 语言集成查询 (LINQ) 不仅可用于检索数据,  而且还是一个功能强大的数据转换工具.  通过使用 LINQ 查询,您可以将源序列用作输入,并采用多种方式修改它以创建新的输出序列.您可以通过排序和分组来修改该序列,而不必修改元素本身.但是,LINQ 查询的最强大的功能是能够创建新类型.这一功能在 select 子句中实现. 例如,可以执行下列任务: 一. 将多个输入联接到一个输出序列 1 class Student 2 { 3 publ

Linq技术三:Linq to Object 和生成数据表的扩展方法

这篇来谈论一下Linq第三个方面的应用:Linq to Object,只要是继承了IEnumerable或IQueryable接口的Object都能使用Linq特性进行操作.在操作过程当中可能很多人都觉得不好调试不能实时地观察结果数据集,想把IQuery的Linq查询语句转换成数据表DataTable,要怎么实现转换呢?来看一下. 先来说一场景解释一下为什么需要用Linq来解决一些问题,能解决一些什么样的问题,相对于SQL,DataTable等一些传统操作方式有哪些优势? 场景:目前主要数据源有

SQL基础三(例子)

-------------------对分组统计的结果进一步筛选(having子句使用)------------------------------ select * from student2010 --1.查询qypt08class表中各院系的人数,只显示人数多于400的记录 select yx,sum(rs) from qypt08class group by yx having sum(rs)>400 --2.统计stucou表中各门课程的选修人数,只显示人数少于30的记录(显示coun

linq学习三个实例

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LinqExer2 { class Program { static void Main(string[] args) { //1.LINQ实例一 //int[] number = { 2, 4, 3, 5, 7, 21, 34 }; //var

Object Pascal 语法之语言基础(三)

1.6 Object Pascal 的运算符 运算符是程序代码中对各种类型的数据进行计算的符号,通常分为算数运算符.逻辑运算符.比较运算符和按位运算符. 1.算术运算符Object Pascal 语言的算术运算符,如表1-9 所示.表1-9 Object Pascal 语言算术运算符 操作符 操作 操作数据类型 结果类型 + 加 整型.实型 整型.实型 - 减 整型.实型 整型.实型 * 乘 整型.实型 整型.实型 / 除 整型.实型 整型.实型 mod 取余 整型 整型 div 整除 整型 整