随着多核时代的到来,并行开发越来越展示出它的强大威力!使用并行程序,充分的利用系统资源,提高程序的性能。在.net 4.0中,微软给我们提供了一个新的命名空间:System.Threading.Tasks。这里面有很多关于并行开发的东西,今天第一篇就介绍下最基础,最简单的——认识和使用Parallel。 一、 Parallel的使用 在Parallel下面有三个常用的方法invoke,For和ForEach。 1、 Parallel.Invoke 这是最简单,最简洁的将串行的代码并行化。 在这里先讲一个知识点,就是StopWatch的使用,最近有一些人说找不到StopWatch,StopWatch到底是什么东西,今天就来说明一下。 StopWatch在System.Diagnostics命名控件,要使用它就要先引用这个命名空间。 其使用方法如下: var stopWatch = new StopWatch(); //创建一个Stopwatch实例 stopWatch.Start(); //开始计时 stopWatch.Stop(); //停止计时 stopWatch.Reset(); //重置StopWatch stopWatch.Restart(); //重新启动被停止的StopWatch stopWatch.ElapsedMilliseconds //获取stopWatch从开始到现在的时间差,单位是毫秒 本次用到的就这么多知识点,想了解更多关于StopWatch的,去百度一下吧,网上有很多资料。 下面进入整体,开始介绍Parallel.Invoke方法,废话不多说了,首先新建一个控制台程序,添加一个类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class ParallelDemo { private Stopwatch stopWatch = new Stopwatch(); public void Run1() { Thread.Sleep(2000); Console.WriteLine("Task 1 is cost 2 sec"); } public void Run2() { Thread.Sleep(3000); Console.WriteLine("Task 2 is cost 3 sec"); } public void ParallelInvokeMethod() { stopWatch.Start(); Parallel.Invoke(Run1, Run2); stopWatch.Stop(); Console.WriteLine("Parallel run " + stopWatch.ElapsedMilliseconds + " ms."); stopWatch.Restart(); Run1(); Run2(); stopWatch.Stop(); Console.WriteLine("Normal run " + stopWatch.ElapsedMilliseconds + " ms."); } } |
代码很简单,首先新加一个类,在类中写了两个方法,Run1和Run2,分别等待一定时间,输出一条信息,然后写了一个测试方法ParallelInvokeMethod,分别用两种方法调用Run1和Run2,然后在main方法中调用,下面来看一下运行时间如何: 大家应该能够猜到,正常调用的话应该是5秒多,而Parallel.Invoke方法调用用了只有3秒,也就是耗时最长的那个方法,可以看出方法是并行执行的,执行效率提高了很多。 2、Parallel.For 这个方法和For循环的功能相似,下面就在类中添加一个方法来测试一下吧。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public void ParallelForMethod() { stopWatch.Start(); for (int i = 0; i < 10000; i++) { for (int j = 0; j < 60000; j++) { int sum = 0; sum += i; } } stopWatch.Stop(); Console.WriteLine("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms."); stopWatch.Reset(); stopWatch.Start(); Parallel.For(0, 10000, item => { for (int j = 0; j < 60000; j++) { int sum = 0; sum += item; } }); stopWatch.Stop(); Console.WriteLine("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms."); } |
写了两个循环,做了一些没有意义的事情,目的主要是为了消耗CPU时间,同理在main方法中调用,运行结果如下图: 可以看到,Parallel.For所用的时间比单纯的for快了1秒多,可见提升的性能是非常可观的。那么,是不是Parallel.For在任何时候都比for要快呢?答案当然是“不是”,要不然微软还留着for干嘛? 下面修改一下代码,添加一个全局变量num,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public void ParallelForMethod() { var obj = new Object(); long num = 0; ConcurrentBag<long> bag = new ConcurrentBag<long>(); stopWatch.Start(); for (int i = 0; i < 10000; i++) { for (int j = 0; j < 60000; j++) { //int sum = 0; //sum += item; num++; } } stopWatch.Stop(); Console.WriteLine("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms."); stopWatch.Reset(); stopWatch.Start(); Parallel.For(0, 10000, item => { for (int j = 0; j < 60000; j++) { //int sum = 0; //sum += item; lock (obj) { num++; } } }); stopWatch.Stop(); Console.WriteLine("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms."); } |
Parallel.For由于是并行运行的,所以会同时访问全局变量num,为了得到正确的结果,要使用lock,此时来看看运行结果: 是不是大吃一惊啊?Parallel.For竟然用了15秒多,而for跟之前的差不多。这主要是由于并行同时访问全局变量,会出现资源争夺,大多数时间消耗在了资源等待上面。 一直说并行,那么从哪里可以看出来Parallel.For是并行执行的呢?下面来写个测试代码:
1 2 3 4 |
Parallel.For(0, 100, i => { Console.Write(i + "\t"); }); |
从0输出到99,运行后会发现输出的顺序不对,用for顺序肯定是对的,并行同时执行,所以会出现输出顺序不同的情况。 2、Parallel.Foreach 这个方法跟Foreach方法很相似,想具体了解的,可以百度些资料看看,这里就不多说了,下面给出其使用方法:
1 2 3 4 5 6 |
List<int> list = new List<int>(); list.Add(0); Parallel.ForEach(list, item => { DoWork(item); }); |
二、 Parallel中途退出循环和异常处理 1、当我们使用到Parallel,必然是处理一些比较耗时的操作,当然也很耗CPU和内存,如果我们中途向停止,怎么办呢? 在串行代码中我们break一下就搞定了,但是并行就不是这么简单了,不过没关系,在并行循环的委托参数中提供了一个ParallelLoopState, 该实例提供了Break和Stop方法来帮我们实现。 Break: 当然这个是通知并行计算尽快的退出循环,比如并行计算正在迭代100,那么break后程序还会迭代所有小于100的。 Stop:这个就不一样了,比如正在迭代100突然遇到stop,那它啥也不管了,直接退出。 下面来写一段代码测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void ParallelBreak() { ConcurrentBag<int> bag = new ConcurrentBag<int>(); stopWatch.Start(); Parallel.For(0, 1000, (i, state) => { if (bag.Count == 300) { state.Stop(); return; } bag.Add(i); }); stopWatch.Stop(); Console.WriteLine("Bag count is " + bag.Count + ", " + stopWatch.ElapsedMilliseconds); } |
这里使用的是Stop,当数量达到300个时,会立刻停止;可以看到结果"Bag count is 300",如果用break,可能结果是300多个或者300个,大家可以测试一下。 2、异常处理 首先任务是并行计算的,处理过程中可能会产生n多的异常,那么如何来获取到这些异常呢?普通的Exception并不能获取到异常,然而为并行诞生的AggregateExcepation就可以获取到一组异常。 这里我们修改Parallel.Invoke的代码,修改后代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
public class ParallelDemo { private Stopwatch stopWatch = new Stopwatch(); public void Run1() { Thread.Sleep(2000); Console.WriteLine("Task 1 is cost 2 sec"); throw new Exception("Exception in task 1"); } public void Run2() { Thread.Sleep(3000); Console.WriteLine("Task 2 is cost 3 sec"); throw new Exception("Exception in task 2"); } public void ParallelInvokeMethod() { stopWatch.Start(); try { Parallel.Invoke(Run1, Run2); } catch (AggregateException aex) { foreach (var ex in aex.InnerExceptions) { Console.WriteLine(ex.Message); } } stopWatch.Stop(); Console.WriteLine("Parallel run " + stopWatch.ElapsedMilliseconds + " ms."); stopWatch.Reset(); stopWatch.Start(); try { Run1(); Run2(); } catch(Exception ex) { Console.WriteLine(ex.Message); } stopWatch.Stop(); Console.WriteLine("Normal run " + stopWatch.ElapsedMilliseconds + " ms."); } } |
顺序调用方法我把异常处理写一起了,这样只能捕获Run1的异常信息,大家可以分开写。捕获AggregateException 异常后,用foreach循环遍历输出异常信息,可以看到两个异常信息都显示了。 点击这里,下载源码 from:http://www.cnblogs.com/yunfeifei/p/3993401.html
View DetailsMath.Round(45.367,2) //Returns 45.37 Math.Round(45.365,2) //Returns 45.36 C#中的Round()不是我们中国人理解的四舍五入,是老外的四舍五入,是符合IEEE标准的四舍五入,具体是四舍六入,下面的才是符合中国人理解的四舍五入 Math.Round(45.367,2,MidpointRounding.AwayFromZero);//45.37 Math.Round(45.365,2,MidpointRounding.AwayFromZero) //Returns 45.37 from:http://www.cnblogs.com/purplefox2008/archive/2011/08/08/2130635.html
View Details第三部分:使用多线程 1. 单元模式和Windows Forms 单元模式线程是一个自动线程安全机制, 非常贴近于COM——Microsoft的遗留下的组件对象模型。尽管.NET最大地放弃摆脱了遗留下的模型,但很多时候它也会突然出现,这是因为有必要与旧的API 进行通信。单元模式线程与Windows Forms最相关,因为大多Windows Forms使用或包装了长期存在的Win32 API——连同它的单元传统。 单元是多线程的逻辑上的“容器”,单元产生两种容量——“单的”和“多的”。单线 程单元只包含一个线程;多线程单元可以包含任何数量的线程。单线程模式更普遍 并且能与两者有互操作性。 就像包含线程一样,单元也包含对象,当对象在一个单元内被创建后,在它的生命周期中它将一直存在在那,永远也“居家不出”地与那些驻留线程在一起。这类似于被包含在.NET 同步环境中 ,除了同步环境中没有自己的或包含线程。任何线程可以访问在任何同步环境中的对象 ——在排它锁的控制中。但是单元内的对象只有单元内的线程才可以访问。 想象一个图书馆,每本书都象征着一个对象;借出书是不被允许的,书都在图书馆 创建并直到它寿终正寝。此外,我们用一个人来象征一个线程。 一个同步内容的图书馆允许任何人进入,同时同一时刻只允许一个人进入,在图书馆外会形成队列。 单元模式的图书馆有常驻维护人员——对于单线程模式的图书馆有一个图书管理员, 对于多线程模式的图书馆则有一个团队的管理员。没人被允许除了隶属与维护人员的人 ——资助人想要完成研究就必须给图书管理员发信号,然后告诉管理员去做工作!给管理员发信号被称为调度编组——资助人通过调度把方法依次读出给一个隶属管理员的人(或,某个隶属管理员的人!)。 调度编组是自动的,在Windows Forms通过信息泵被实现在库结尾。这就是操作系统经常检查键盘和鼠标的机制。如果信息到达的太快了,以致不能被处理,它们将形成消息队列,所以它们可以以它们到达的顺序被处理。 1.1 定义单元模式 .NET线程在进入单元核心Win32或旧的COM代码前自动地给单元赋值,它被默认地指定为多线程单元模式,除非需要一个单线程单元模式,就像下面的一样: 1 2 Thread t = new Thread (…); t.SetApartmentState (ApartmentState.STA); 你也可以用STAThread特性标在主线程上来让它与单线程单元相结合: 1 2 3 4 class Program { [STAThread] static void Main() { … 线程单元设置对纯.NET代码没有效果,换言之,即使两个线程都有STA 的单元状态,也可以被相同的对象同时调用相同的方法,就没有自动的信号编组或锁定发生了, 只有在执行非托管的代码时,这才会发生。 在System.Windows.Forms名称空间下的类型,广泛地调用Win32代码, 在单线程单元下工作。由于这个原因,一个Windos Forms程序应该在它的主方法上贴上 [STAThread]特性,除非在执行Win32 UI代码之前以下二者之一发生了: 它将调度编组成一个单线程单元 它将崩溃 1.2 Control.Invoke 在多线程的Windows Forms程序中,通过非创建控件的线程调用控件的的属性和方法是非法的。所有跨进程的调用必须被明确地排列至创建控件的线程中(通常为主线程),利用Control.Invoke 或 Control.BeginInvoke方法。你不能依赖自动调度编组因为它发生的太晚了,仅当执行刚好进入了非托管的代码它才发生,而.NET已有足够的时间来运行“错误的”线程代码,那些非线程安全的代码。 一个优秀的管理Windows Forms程序的方案是使用BackgroundWorker, 这个类包装了需要报道进度和完成度的工作线程,并自动地调用Control.Invoke方法作为需要。 1.3 BackgroundWorker BackgroundWorker是一个在System.ComponentModel命名空间 下帮助类,它管理着工作线程。它提供了以下特性: "cancel" 标记,对于给工作线程打信号让它结束而没有使用 Abort的情况 提供报道进度,完成度和退出的标准方案 实现了IComponent接口,允许它参与Visual Studio设计器 […]
View Details1.同步要领 下面的表格列展了.NET对协调或同步线程动作的可用的工具: 简易阻止方法 构成 目的 Sleep 阻止给定的时间周期 Join 等待另一个线程完成 锁系统 构成 目的 跨进程? 速度 lock 确保只有一个线程访问某个资源或某段代码。 否 快 Mutex 确保只有一个线程访问某个资源或某段代码。可被用于防止一个程序的多个实例同时运行。 是 中等 Semaphore 确保不超过指定数目的线程访问某个资源或某段代码。 是 中等 (同步的情况下也提够自动锁。) 信号系统 构成 目的 跨进程? 速度 EventWaitHandle 允许线程等待直到它受到了另一个线程发出信号。 是 中等 Wait 和 Pulse* 允许一个线程等待直到自定义阻止条件得到满足。 否 中等 非阻止同步系统* 构成 目的 跨进程? 速度 Interlocked* 完成简单的非阻止原子操作。 是(内存共享情况下) 非常快 volatile* 允许安全的非阻止在锁之外使用个别字段。 非常快 * 代表页面将转到第四部分 1.1 阻止 (Blocking) 当一个线程通过上面所列的方式处于等待或暂停的状态,被称为被阻止。一旦被阻止,线程立刻放弃它被分配的 CPU时间,将它的ThreadState属性添加为WaitSleepJoin状态,不在安排时间直到停止阻止。停止阻止在任意四种 情况下发生(关掉电脑的电源可不算!): 阻止的条件已得到满足 操作超时(如果timeout被指定了) 通过Thread.Interrupt中断了 通过Thread.Abort放弃了 当线程通过(不建议)Suspend 方法暂停,不认为是被阻止了。 1.2 休眠 和 轮询 调用Thread.Sleep阻止当前的线程指定的时间(或者直到中断): 1 2 3 4 5 6 static void […]
View Details文章系参考转载,英文原文网址请参考:http://www.albahari.com/threading/ 作者 Joseph Albahari, 翻译 Swanky Wu 中文翻译作者把原文放在了"google 协作"上面,GFW屏蔽,不能访问和查看,因此我根据译文和英文原版整理转载到园子里面。 本系列文章可以算是一本很出色的C#线程手册,思路清晰,要点都有介绍,看了后对C#的线程及同步等有了更深入的理解。 入门 概述与概念 创建和开始使用多线程 线程同步基础 同步要领 锁和线程安全 Interrupt 和 Abort 线程状态 等待句柄 同步环境 使用多线程 单元模式和Windows Forms BackgroundWorker类 ReaderWriterLock类 线程池 异步委托 计时器 局部储存 高级话题 非阻止同步 Wait和Pulse Suspend和Resume 终止线程 一、入门 1. 概述与概念 C#支持通过多线程并行地执行代码,一个线程有它独立的执行路径,能够与其它的线程同时地运行。一个C#程序开始于一个单线程,这个单线程是被CLR和操作系统(也称为“主线程”)自动创建的,并具有多线程创建额外的线程。这里的一个简单的例子及其输出: 除非被指定,否则所有的例子都假定以下命名空间被引用了: using System; using System.Threading; 1 2 3 4 5 6 7 8 9 10 11 class ThreadTest { static void Main() { Thread t = new Thread (WriteY); t.Start(); // Run WriteY on the new thread while (true) Console.Write ("x"); // Write 'x' forever } static […]
View Details一、多线程带来的问题 1、死锁问题 前面我们学习了Task的使用方法,其中Task的等待机制让我们瞬间爱上了它,但是如果我们在调用Task.WaitAll方法等待所有线程时,如果有一个Task一直不返回,会出现什么情况呢?当然,如果我们不做出来的话,程序会一直等待下去,那么因为这一个Task的死锁,导致其他的任务也无法正常提交,整个程序"死"在那里。下面我们来写一段代码,来看一下死锁的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var t1 = Task.Factory.StartNew(() => { Console.WriteLine("Task 1 Start running..."); while(true) { System.Threading.Thread.Sleep(1000); } Console.WriteLine("Task 1 Finished!"); }); var t2 = Task.Factory.StartNew(() => { Console.WriteLine("Task 2 Start running..."); System.Threading.Thread.Sleep(2000); Console.WriteLine("Task 2 Finished!"); }); Task.WaitAll(t1,t2); |
这里我们创建两个Task,t1和t2,t1里面有个while循环,由于条件一直为TRUE,所以他永远也无法退出。运行程序,结果如下: 可以看到Task2完成了,就是迟迟等不到Task1,这个时候我们按回车是没有反应的,除非关掉窗口。如果我们在项目中遇到这种情况是令人很纠结的,因为我们也不知道到底发生了什么,程序就是停在那里,也不报错,也不继续执行。 那么出现这种情况我们该怎么处理呢?我们可以设置最大等待时间,如果超过了等待时间,就不再等待,下面我们来修改代码,设置最大等待时间为5秒(项目中可以根据实际情况设置),如果超过5秒就输出哪个任务出错了,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
Task[] tasks = new Task[2]; var tasks[0] = Task.Factory.StartNew(() => { Console.WriteLine("Task 1 Start running..."); while(true) { System.Threading.Thread.Sleep(1000); } Console.WriteLine("Task 1 Finished!"); }); var tasks[1] = Task.Factory.StartNew(() => { Console.WriteLine("Task 2 Start running..."); System.Threading.Thread.Sleep(2000); Console.WriteLine("Task 2 Finished!"); }); Task.WaitAll(tasks,5000); for (int i = 0; i < tasks.Length;i++ ) { if (tasks[i].Status != TaskStatus.RanToCompletion) { Console.WriteLine("Task {0} Error!",i + 1); } } Console.Read(); |
这里我们将所有任务放到一个数组里面进行管理,调用Task.WaitAll的一个重载方法,第一个参数是Task[]数据,第二个参数是最大等待时间,单位是毫秒,这里我们设置为5000及等待5秒钟,就继续向下执行。下面我们遍历Task数组,通过Status属性判断哪些Task没有完成,然后输出错误信息。 2、SpinLock(自旋锁) 我们初识多线程或者多任务时,第一个想到的同步方法就是使用lock或者Monitor,然而在4.0 之后微软给我们提供了另一把利器——spinLock,它比重量级别的Monitor具有更小的性能开销,它的用法跟Monitor很相似,VS给的提示如下: 下面我们来写一个例子看一下,代码如下(关于lock和Monitor的用法就不再细说了,网上资料很多,大家可以看看):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
SpinLock slock = new SpinLock(false); long sum1 = 0; long sum2 = 0; Parallel.For(0, 100000, i => { sum1 += i; }); Parallel.For(0, 100000, i => { bool lockTaken = false; try { slock.Enter(ref lockTaken); sum2 += i; } finally { if (lockTaken) slock.Exit(false); } }); Console.WriteLine("Num1的值为:{0}", sum1); Console.WriteLine("Num2的值为:{0}", sum2); Console.Read(); |
输出结果如图: 这里我们使用了Parallel.For方法来做演示,Parallel.For用起来方便,但是在实际开发中还是尽量少用,因为它的不可控性太高,有点简单粗暴的感觉,可能带来一些不必要的"麻烦",最好还是使用Task,因为Task的可控性较好。 slock.Enter方法,解释如下: 3、多线程之间的数据同步 多线程间的同步,在用thread的时候,我们常用的有lock和Monitor,上面刚刚介绍了.Net4.0中一个新的锁——SpinLock(自旋锁),实际上,我们还可以将任务分成多块,由多个线程一起执行,最后合并多个线程的结果,如:求1到100的和,我们分10个线程,分别求1~10,……,90~100的和,然后合并十个线程的结果。还有就是使用线程安全集合,可参加第二天的文章。其实Task的同步机制做已经很好了,如果有特殊业务需求,有线程同步问题,大家可一起交流~~ 二、Task和线程池之间的抉择 我们要说的task的知识也说的差不多了,接下来我们开始站在理论上了解下“线程池”和“任务”之间的关系,我们要做到知其然,还要知其所以然。不管是说线程还是任务,我们都不可避免的要讨论下线程池,然而在.net 4.0以后,线程池引擎考虑了未来的扩展性,已经充分利用多核微处理器架构,只要在可能的情况下,我们应该尽量使用task,而不是线程池。 这里简要的分析下CLR线程池,其实线程池中有一个叫做“全局队列”的概念,每一次我们使用QueueUserWorkItem的使用都会产生一个“工作项”,然后“工作项”进入“全局队列”进行排队,最后线程池中的的工作线程以FIFO(First Input First Output)的形式取出,这里值得一提的是在.net 4.0之后“全局队列”采用了无锁算法,相比以前版本锁定“全局队列”带来的性能瓶颈有了很大的改观。那么任务委托的线程池不光有“全局队列”,而且每一个工作线程都有”局部队列“。我们的第一反应肯定就是“局部队列“有什么好处呢?这里暂且不说,我们先来看一下线程池中的任务分配,如下图: 线程池的工作方式大致如下,线程池的最小线程数是6,线程1~3正在执行任务1~3,当有新的任务时,就会向线程池请求新的线程,线程池会将空闲线程分配出去,当线程不足时,线程池就会创建新的线程来执行任务,直到线程池达到最大线程数(线程池满)。总的来说,只有有任务就会分配一个线程去执行,当FIFO十分频繁时,会造成很大的线程管理开销。 下面我们来看一下task中是怎么做的,当我们new一个task的时候“工作项”就会进去”全局队列”,如果我们的task执行的非常快,那么“全局队列“就会FIFO的非常频繁,那么有什么办法缓解呢?当我们的task在嵌套的场景下,“局部队列”就要产生效果了,比如我们一个task里面有3个task,那么这3个task就会存在于“局部队列”中, 如下图的任务一,里面有三个任务要执行,也就是产生了所谓的"局部队列",当任务三的线程执行完成时,就会从任务一种的队列中以FIFO的形式"窃取"任务执行 ,从而减少了线程管理的开销。这就相当于,有两个人,一个人干完了分配给自己的所有活,而另一个人却还有很多的活,闲的人应该接手点忙的人的活,一起快速完成。 从上面种种情况我们看到,这些分流和负载都是普通ThreadPool.QueueUserWorkItem所不能办到的,所以说在.net 4.0之后,我们尽可能的使用TPL,抛弃ThreadPool。 这是5天玩转C#并行和多线程编程系列的最后一篇了,当然还有很多东西没说到,如果真的想要玩转多线程,还是要多多努力学习的。大家在学习过程中有什么问题可以一起交流~~ 如果大家感觉我的博文对大家有帮助,请推荐支持一把,给我写作的动力。 from:http://www.tuicool.com/articles/2maqYrI
View Details(1)不需要传递参数,也不需要返回参数 ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回值。 复制代码代码如下: class Program{ static void Main(string[] args) { for (int i = 0; i < 30; i++) { ThreadStart threadStart = new ThreadStart(Calculate); Thread thread = new Thread(threadStart); thread.Start(); } Thread.Sleep(2000); Console.Read(); } public static void Calculate() { DateTime time = DateTime.Now;//得到当前时间 Random ra = new Random();//随机数对象 Thread.Sleep(ra.Next(10,100));//随机休眠一段时间 Console.WriteLine(time.Minute + ":" + time.Millisecond); } } (2)需要传递单个参数 ParameterThreadStart委托定义为void ParameterizedThreadStart(object state),有一个参数但是没有返回值。 复制代码代码如下: class Program{ static void Main(string[] args) { for (int i = 0; i < 30; i++) { ParameterizedThreadStart tStart = new ParameterizedThreadStart(Calculate); Thread thread = […]
View Details在网上看到的解决方案基本都是说host文件中127.0.0.1 localhost 的对应, 但我查看了host文件,并没有问题,纠结半天,拿查询语句在Mysql Workbench上执行,报了一个错,说是子查询包含多个结果,我去~!这很简单的一个错误asp.net竟然没能准确反馈。 于是在子查询语句后面加limit 1,Ok~ 所以,出现“由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败”这个错误时,有可能是查询语句有问题,而不是连接有问题。 from:http://blog.csdn.net/idoiknow/article/details/8923553
View Details1、整个Excel表格叫做工作表:WorkBook(工作薄),包含的叫页(工作表):Sheet;行:Row;单元格Cell。 2、NPOI是POI的C#版本,NPOI的行和列的index都是从0开始 3、POI读取Excel有两种格式一个是HSSF,另一个是XSSF。 HSSF和XSSF的区别如下: HSSF is the POI Project’s pure Java implementation of the Excel ’97(-2007) file format. XSSF is the POI Project’s pure Java implementation of the Excel 2007 OOXML (.xlsx) file format. 即:HSSF适用2007以前的版本,XSSF适用2007版本及其以上的。 下面是用NPOI读写Excel的例子:ExcelHelper封装的功能主要是把DataTable中数据写入到Excel中,或者是从Excel读取数据到一个DataTable中。 ExcelHelper类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using NPOI.HSSF.UserModel; using System.IO; using System.Data; namespace NetUtilityLib { public class ExcelHelper : IDisposable { private string fileName = null; //文件名 private IWorkbook workbook = null; private FileStream fs = null; private bool disposed; public ExcelHelper(string fileName) { this.fileName = fileName; disposed = false; } /// <summary> /// 将DataTable数据导入到excel中 /// </summary> /// <param name="data">要导入的数据</param> /// <param name="isColumnWritten">DataTable的列名是否要导入</param> /// <param name="sheetName">要导入的excel的sheet的名称</param> /// <returns>导入数据行数(包含列名那一行)</returns> public int DataTableToExcel(DataTable data, string sheetName, bool isColumnWritten) { int i = 0; int j = 0; int count = 0; ISheet sheet = null; fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); if (fileName.IndexOf(".xlsx") > 0) // 2007版本 workbook = new XSSFWorkbook(); else if (fileName.IndexOf(".xls") > 0) // 2003版本 workbook = new HSSFWorkbook(); try { if (workbook != null) { sheet = workbook.CreateSheet(sheetName); } else { return -1; } if (isColumnWritten == true) //写入DataTable的列名 { IRow row = sheet.CreateRow(0); for (j = 0; j < data.Columns.Count; ++j) { row.CreateCell(j).SetCellValue(data.Columns[j].ColumnName); } count = 1; } else { count = 0; } for (i = 0; i < data.Rows.Count; ++i) { IRow row = sheet.CreateRow(count); for (j = 0; j < data.Columns.Count; ++j) { row.CreateCell(j).SetCellValue(data.Rows[i][j].ToString()); } ++count; } workbook.Write(fs); //写入到excel return count; } catch (Exception ex) { Console.WriteLine("Exception: " + ex.Message); return -1; } } /// <summary> /// 将excel中的数据导入到DataTable中 /// </summary> /// <param name="sheetName">excel工作薄sheet的名称</param> /// <param name="isFirstRowColumn">第一行是否是DataTable的列名</param> /// <returns>返回的DataTable</returns> public DataTable ExcelToDataTable(string sheetName, bool isFirstRowColumn) { ISheet sheet = null; DataTable data = new DataTable(); int startRow = 0; try { fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); if (fileName.IndexOf(".xlsx") > 0) // 2007版本 workbook = new XSSFWorkbook(fs); else if (fileName.IndexOf(".xls") > 0) // 2003版本 workbook = new HSSFWorkbook(fs); if (sheetName != null) { sheet = workbook.GetSheet(sheetName); if (sheet == null) //如果没有找到指定的sheetName对应的sheet,则尝试获取第一个sheet { sheet = workbook.GetSheetAt(0); } } else { sheet = workbook.GetSheetAt(0); } if (sheet != null) { IRow firstRow = sheet.GetRow(0); int cellCount = firstRow.LastCellNum; //一行最后一个cell的编号 即总的列数 if (isFirstRowColumn) { for (int i = firstRow.FirstCellNum; i < cellCount; ++i) { ICell cell = firstRow.GetCell(i); if (cell != null) { string cellValue = cell.StringCellValue; if (cellValue != null) { DataColumn column = new DataColumn(cellValue); data.Columns.Add(column); } } } startRow = sheet.FirstRowNum + 1; } else { startRow = sheet.FirstRowNum; } //最后一列的标号 int rowCount = sheet.LastRowNum; for (int i = startRow; i <= rowCount; ++i) { IRow row = sheet.GetRow(i); if (row == null) continue; //没有数据的行默认是null DataRow dataRow = data.NewRow(); for (int j = row.FirstCellNum; j < cellCount; ++j) { if (row.GetCell(j) != null) //同理,没有数据的单元格都默认是null dataRow[j] = row.GetCell(j).ToString(); } data.Rows.Add(dataRow); } } return data; } catch (Exception ex) { Console.WriteLine("Exception: " + ex.Message); return null; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { if (fs != null) fs.Close(); } fs = null; disposed = true; } } } } |
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; namespace NPOIExcelExample { class Program { static DataTable GenerateData() { DataTable data = new DataTable(); for (int i = 0; i < 5; ++i) { data.Columns.Add("Columns_" + i.ToString(), typeof(string)); } for (int i = 0; i < 10; ++i) { DataRow row = data.NewRow(); row["Columns_0"] = "item0_" + i.ToString(); row["Columns_1"] = "item1_" + i.ToString(); row["Columns_2"] = "item2_" + i.ToString(); row["Columns_3"] = "item3_" + i.ToString(); row["Columns_4"] = "item4_" + i.ToString(); data.Rows.Add(row); } return data; } static void PrintData(DataTable data) { if (data == null) return; for (int i = 0; i < data.Rows.Count; ++i) { for (int j = 0; j < data.Columns.Count; ++j) Console.Write("{0} ", data.Rows[i][j]); Console.Write("\n"); } } static void TestExcelWrite(string file) { try { using (ExcelHelper excelHelper = new ExcelHelper(file)) { DataTable data = GenerateData(); int count = excelHelper.DataTableToExcel(data, "MySheet", true); if (count > 0) Console.WriteLine("Number of imported data is {0} ", count); } } catch (Exception ex) { Console.WriteLine("Exception: " + ex.Message); } } static void TestExcelRead(string file) { try { using (ExcelHelper excelHelper = new ExcelHelper(file)) { DataTable dt = excelHelper.ExcelToDataTable("MySheet", true); PrintData(dt); } } catch (Exception ex) { Console.WriteLine("Exception: " + ex.Message); } } static void Main(string[] args) { string file = "..\\..\\myTest.xlsx"; TestExcelWrite(file); TestExcelRead(file); } } } |
Excel相关DLL下载:NPOI-Lib.rar 1.NPOI下载地址:http://npoi.codeplex.com/releases/view/38113 2.NPOI学习系列教程推荐:http://www.cnblogs.com/tonyqus/archive/2009/04/12/1434209.html 参考: http://www.cnblogs.com/Erik_Xu/archive/2012/06/08/2541957.html http://www.cnblogs.com/linzheng/archive/2010/12/20/1912137.html http://www.cnblogs.com/knowledgesea/archive/2012/11/16/2772547.html from:http://www.cnblogs.com/luxiaoxun/p/3374992.html
View Details原文地址: http://msdn.microsoft.com/en-us/library/gg715124(v=vs.103) 使用 EF 4.1 或者更新版本, 你可以直接执行任何数据库命令. 在本节介绍的方法允许你对数据库执行原生的 SQL 命令. 通过 SQL 查询语句获取实体对象集 DbSet 类中的 SqlQuery 方法允许你执行一个返回实体对象集的原生 SQL 查询. 默认情况下, 返回的对象集会被上下文跟踪; 这可以通过对方法返回的 DbSqlQuery 对象调用 AsNoTracking 方法取消.返回的结果集一般为 DbSet 所对应的类型, 否则即便是其派生类也无法返回. 如果所查询的表包含了其他实体类型的数据, 那么所执行的 SQL 语句应该被正确书写, 保证只返回指定类型实体的数据. 下面的例子使用 SqlQuery 方法执行了一个 SQL 查询, 返回一个 Department 类型的实例集.
1 2 3 4 5 |
1 using (var context = new SchoolEntities()) 2 { 3 var departments = context.Departments.SqlQuery( 4 "select * from Department").ToList(); 5 } |
译注: AsNoTracking 方法必须再查询执行前调用, 查询执行后调用无效. 通过 SQL 查询获取非实体对象集 通过 Database 类中的 SqlQuery 方法来执行原生 SQL 命令, 可以返回任何类型的实例, 包括 .Net 中的原生类型. 但获取的数据将不会被上下文对象跟踪, 即使我们用这个方法来检索实体对象. 如:
1 2 3 4 |
1 using (var context = new SchoolEntities()) 2 { 3 var names = context.Database.SqlQuery<string>("select Name from Department").ToList(); 4 } |
让数据库执行原生的非查询 SQL 命令 可以通过 Database 类中的 ExecuteSqlCommand 方法执行非查询命令. 例如:
1 2 3 4 |
1 using (var context = new SchoolEntities()) 2 { 3 context.Database.ExecuteSqlCommand("update Department set Name = 'Mathematics' where Name = 'Math'"); 4 } |
ExecuteSqlCommand 方法有时会被用在 Code First 创建的数据库的初始化函数中, 用来对数据库进行一些额外的配置 (例如, 设置索引). 需要注意的是, 上下文对象并不知道执行了 ExecuteSqlCommand […]
View Details