c#中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基础(二)(https://www.jb51.net/article/244215.htm)中的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()方法。

到此这篇关于c#中LINQ的基本用法的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • c#中LINQ的基本用法(二)

    目录 1.筛选 2.用索引筛选 3.类型筛选 4.复合的from子句 5.排序 6.分组 7.对嵌套的对象分组 8.内连接 9.左连接 10.组连接 11.集合操作 12.合并 13.分区 14.聚合操作符 15.转换操作符 16.生成操作符 本文主要介绍LINQ查询操作符 LINQ查询为最常用的操作符定义了一个声明语法.还有许多查询操作符可用于Enumerable类.下面的例子需要用到LINQ基础(一)(https://www.jb51.net/article/244208.htm)的一些代码

  • c# Linq查询详解

    c#提供的ling查询极大的遍历了集合的查询过程,且使用简单方便,非常的有用. 下面将分别用简单的例子说明:ling基本查询.延迟查询属性.类型筛选.复合from字句.多级排序.分组查询.联合查询.合并.分页.聚合操作符.并行linq.取消长时间运行的并行ling查询. Lambda表达式简介: /*Lambda表达式:Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数 表达式位于 => 运算符右侧的 lambda 表达式称为"表达式 lambda". * (i

  • c# 动态构建LINQ查询表达式

    作者:精致码农 出处:http://cnblogs.com/willick 联系:liam.wang@live.com 最近工作中遇到一个这样的需求:在某个列表查询功能中,可以选择某个数字列(如商品单价.当天销售额.当月销售额等),再选择 小于或等于 和 大于或等于 ,再填写一个待比较的数值,对数据进行查询过滤. 如果只有一两个这样的数字列,那么使用 Entity Framework Core 可以这么写 LINQ 查询: public Task<List<Product>> Ge

  • 全面分析c# LINQ

    大家好,这是 [C#.NET 拾遗补漏] 系列的第 08 篇文章,今天讲 C# 强大的 LINQ 查询.LINQ 是我最喜欢的 C# 语言特性之一. LINQ 是 Language INtegrated Query 单词的首字母缩写,翻译过来是语言集成查询.它为查询跨各种数据源和格式的数据提供了一致的模型,所以叫集成查询.由于这种查询并没有制造新的语言而只是在现有的语言基础上来实现,所以叫语言集成查询. 一些基础 在 C# 中,从功能上 LINQ 可分为两类:LINQ to Object 和 L

  • c# Linq常用的小技巧

    前言 在C#语言发展的历史长河中,Linq是一个极其重要的里程碑! Linq的语法吸取了SQL语法的特性,同时配合Lambda表达式又可以使代码更加优雅! 可以这么说,用好了Linq可以大大提高程序猿的工作效率,毕竟我们的日常工作本质就是对数据的处理.经历了十多年的发展,现在微软自带的内库包含的Linq函数已经非常多了,几乎满足我们日常工作. 下面根据一个对科室数据操作的例子,就个人觉得日常高频使用的Linq小技巧贴出来,权当是做个笔记了. 初始化数据 定义模型 这里定义一个科室对象,模拟我们日

  • c#中LINQ的基本用法实例

    一.什么是LINQ LINQ(读音link)代表语言集成查询(Language Integrated Query),是.NEt框架的扩展,它允许我们用SQL查询数据库的方式来查询数据的集合,使用它,你可以从数据库.程序对象的集合以及XML文档中查询数据 下面一个简单的示例,可以查询数组中小于8的数字并输出. 一般步骤:获取数据源.创建查询.执行查询.需要注意的是,尽管查询在语句中定义,但直到最后的foreach语句请求其结果的时候才会执行 using System; using System.C

  • c#中LINQ的基本用法(一)

    LINQ(Language Integrated Query,语言集成查询),在C#语言中集成了查询语法,可以用相同的语法访问不同的数据源.LINQ提供了不同数据源的抽象层,所以可以使用相同的语法.这里主要介绍LINQ的核心原理和C#中支持C# LINQ查询的语言扩展. 1.语法 使用LINQ查询出来自巴西的所以世界冠军.这里可以使用List<T>类的FindAll()方法,但使用LINQ查询语法更简单 static void LINQQuery() { // var query = from

  • 详解c# PLINQ中的分区

    最近因为比较忙,好久没有写博客了,这篇主要给大家分享一下PLINQ中的分区.上一篇介绍了并行编程,这边详细介绍一下并行编程中的分区和自定义分区. 先做个假设,假设我们有一个200Mb的文本文件需要读取,怎么样才能做到最优的速度呢?对,很显然就是拆分,把文本文件拆分成很多个小文件,充分利用我们计算机中的多核cpu的优势,让每个cpu都充分的利用,达到效率的最大化.然而在PLINQ中也是,我们有一个数据源,如果想进行最大的并行化操作,那么就需要把其拆分为可以多个线程同时访问的多个部分,这就是PLIN

  • c#中LINQ的基本用法(三)

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

  • mybatis 中 foreach collection的用法小结(三种)

    foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合. foreach元素的属性主要有 item,index,collection,open,separator,close. item表示集合中每一个元素进行迭代时的别名,     index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置,     open表示该语句以什么开始,     separator表示在每次进行迭代之间以什么符号作为分隔 符,     close表示以什么结束. 在使用foreach的时候

  • Linq中ToList()和CopyToDataTable()用法详解

    最近在项目中使用了Linq,想把Linq的查询结果直接转换成DataTable对象,通过查找发现Linq有一个CopyToDataTable<T>的泛型方法,该方法只能在T是DataRow的情况下使用,发现了这个方法以后就直接在项目中使用了,但是在使用的过程中发现,如果Linq的查询结果不包含任何DataRow对象的时候,使用CopyToDataTable()方法会报错,代码如下: using System; using System.Collections.Generic; using Sy

  • C#中backgroundWorker类的用法详解

    1.在 WinForms 中,有时要执行耗时的操作,在该操作未完成之前操作用户界面,会导致用户界面停止响应.解决的方法就是新开一个线程,把耗时的操作放到线程中执行,这样就可以在用户界面上进行其它操作.新建线程可以用 Thread 类,可以实现多线程同时操作.简单的方法可以通过 BackgroundWorker 类实现. BackgroundWorker 可以用来更新UI界面,但是通常用来Progressbar(进度条)控件 例如更新UI private void Form1_Load(objec

  • C#中LINQ to DataSet操作及DataTable与LINQ相互转换

    一.DataTable的扩展方法: 1.DataTable转Linq:AsEnumerable 方法 返回的EnumerableRowCollection<DataRow> 实现了 IEnumerable<T>接口.其中泛型参数T是DataRow. 此对象可用在 LINQ表达式或方法查询中. 语法: public static EnumerableRowCollection<DataRow> AsEnumerable (this DataTable source); 在

  • C#中Linq的入门教程

    一.LINQ的体系结构 语言集成查询 (LINQ) (C#) | Microsoft 官方文档 LINQ总共包括五个部分: 程序集 命名空间 描述 LINQ to Objects System.Core.dll System.Linq 提供对内存中集合操作的支持 LINQ to XML System.Xml.Linq.dll System.Xml.Linq 提供对XML数据源的操作的支持 LINQ to SQL System.Data.Linq.dll System.Data.Linq 提供对S

  • Oracle中游标Cursor基本用法详解

    查询 SELECT语句用于从数据库中查询数据,当在PL/SQL中使用SELECT语句时,要与INTO子句一起使用,查询的 返回值被赋予INTO子句中的变量,变量的声明是在DELCARE中.SELECT INTO语法如下: SELECT [DISTICT|ALL]{*|column[,column,...]} INTO (variable[,variable,...] |record) FROM {table|(sub-query)}[alias] WHERE............ PL/SQL

随机推荐