【基础篇】

  • 怎样创建一个线程
  • 受托管的线程与Windows线程
  • 前台线程与后台线程
  • 名为BeginXXX和EndXXX的方法是做什么用的
  • 异步和多线程有什么关联

【WinForm多线程编程篇】

  • 多线程WinForm程序总是抛出InvalidOperationException,怎么解决
  • Invoke和BeginInvoke干什么用的,内部是怎么实现的
  • 每个线程都有消息队列吗
  • 为什么WinForm不允许跨线程修改UI线程控件的值
  • 有没有什么办法可以简化WinForm多线程的开发

【线程池】

  • 线程池的作用是什么
  • 所有进程使用一个共享的线程池,还是每个进程使用独立的线程池
  • 线程池中线程的分类
  • .NET线程池有什么不足

【同步】

  • CLR怎样实现lock(obj)锁定
  • 互斥对象(Mutex)、事件(Event)对象与lock语句的比较

基础篇

怎样创建一个线程

方法一:使用Thread类

   public static void Main(string[] args)
        {
            //方法一:使用Thread类
            ThreadStart threadStart = new ThreadStart(Calculate);//通过ThreadStart委托告诉子线程执行什么方法                        Thread thread = new Thread(threadStart);
            thread.Start();//启动新线程
        }

public static void Calculate()
        {
            Console.Write("执行成功");
            Console.ReadKey();
        }

方法二:使用Delegate.BeginInvoke

   delegate double CalculateMethod(double r);//声明一个委托,表明需要在子线程上执行的方法的函数签名
        static CalculateMethod calcMethod = new CalculateMethod(Calculate);

   static void Main(string[] args)
        {
            //方法二:使用Delegate.BeginInvoke
            //此处开始异步执行,并且可以给出一个回调函数(如果不需要执行什么后续操作也可以不使用回调)
            calcMethod.BeginInvoke(5, new AsyncCallback(TaskFinished), null);
            Console.ReadLine();
        }

   public static double Calculate(double r)
        {
            return 2 * r * Math.PI;
        }
        //线程完成之后回调的函数
        public static void TaskFinished(IAsyncResult result)
        {
            double re = 0;
            re = calcMethod.EndInvoke(result);
            Console.WriteLine(re);
        }

方法三:使用ThreadPool.QueueworkItem

受托管的线程与Windows线程

  .NET应用的线程实际上仍然是Windows线程。但是,当某个线程被CLR所知时,我们将它称为受托管的线程。具体来说,由受托管的代码创建出来的线程就是受托管的线程。不过,一旦该线程执行了受托管的代码它就变成了受托管的线程。

  一个受托管的线程和非受托管的线程的区别在于,CLR将创建一个System.Threading.Thread类的实例来代表并操作前者。在内部实现中,CLR将一个包含了所有受托管线程的列表保存在一个叫做ThreadStore地方。

  CLR确保每一个受托管的线程在任意时刻都在一个AppDomain中执行,但是这并不代表一个线程将永远处在一个AppDomain中,它可以随着时间的推移转到其他的AppDomain中。

前台线程与后台线程

  启动了多个线程的程序在关闭的时候却出现了问题,如果程序退出的时候不关闭线程,那么线程就会一直的存在,但是大多启动的线程都是局部变量,不能一一的关闭,如果调用Thread.CurrentThread.Abort()方法关闭主线程的话,就会出现ThreadAbortException异常。可以通过这个方法:Thread.IsBackground设置线程为后台线程。

  msdn对前台线程和后台线程的解释:托管线程或者是后台线程,或者是前台线程。后台线程不会是托管执行环境处于活动状态,除此之外,后台线程与前台线程是一样的。一旦所有前台进程在托管进程(其中.exe文件时托管程序集)中被停止,系统将停止所有后台线程并关闭。通过设置Thread.IsBackground属性,可以将一个线程指定为后台线程或者前台线程。从非托管代码进入托管执行环境的所有线程都被标记为后台线程。通过创建并启动新的Thread对象而生成的所有线程都是前台线程。

名为BeginXXX和EndXXX的方法是做什么用的

  这是.net的一个异步方法名称规范。

  .net在设计的时候为异步编程设计了一个异步编程模型(APM),比如所有的Stream就是BeginRead,EndRead,Socket,WebRequet,SqlCommand都运用到了这个模式,一般来讲,调用BeginXXX的时候,一般会启动一个异步过程去执行一个操作,EndInvoke可以接受这个异步操作的返回,当然如果异步操作在EndIncoke调用的时候还没有执行完成,EndInvoke会一直等待异步操作完成或者超时。

  .NET的异步编程模型(APM)一般包含BeginXXX,EndXXX,IAsyncResult这三个元素,BeginXXX方法都有返回一个IAsyncResult,而EndXXX都需要接受一个IAsyncResult作为参数。

异步和多线程

  异步有许多种方法,我们可以用进程来做异步,或者使用线程,或者硬件的一些特性,比如在实现异步IO的时候,可以以下两种方案:

  方案一:可以通过初始化一个子线程,然后在子线程里进行IO,而让主线程顺利往下执行,当子线程执行完毕就回调

  方案二:使用硬件的支持(现在许多硬件都有自己的处理器),来实现完全的异步,这时我们只需将IO请求告知硬件驱动程序,然后迅速返回,然后等着硬件IO就绪通知我们就可以了

WinForm多线程编程篇

多线程WinForm程序总是抛出InvalidOperationException,怎么解决

  在WinForm中使用线程时,常常遇到一个问题,当在子线程(非UI线程)中修改一个空间的值:比如修改进度条进度,时会抛出异常。

  解决方法就是利用控件提供的Invoke和BeginInvoke把调用封送回UI线程,也就是让控件属性修改在UI线程上执行。

  例如:

   delegate void changeText(double result);
        public Form1()
        {
            InitializeComponent();
            ThreadStart threadStart = new ThreadStart(Calculate);
            Thread thread = new Thread(threadStart);
            thread.Start();
        }

public void Calculate()
        {
            double r = 2;
            double result = 2 * Math.PI * r;
            CalcFinished(result);
        }
        public void CalcFinished(double result)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new changeText(CalcFinished), result);
            }
            else
            {
                this.textBox1.Text = result.ToString();
            }
        }

  这里用到了Control的一个属性InvokeRequired(这个属性石可以在其它线程里访问),这个属性表明调用是否来自非UI线程,如果是,使用BeginInvoke来调用这个函数,否则就直接调用,省去线程封送的过程。

Invoke和BeginInvoke干什么用的,内部是怎么实现的

  这两个方法主要是让给出的方法在控件创建的线程上执行。

  Invoke使用了Win32API的SendMessage     BeginInvoke使用了Win32API的PostMessage

  这两个方法想UI线程的消息队列中放入一个消息,当UI线程处理这个消息时,就会在自己的上下文中执行传入的方法,换句话说,凡是使用BeginInvoke和Invoke调用的线程都是在UI主线程中执行,所以如果这些方法里涉及一些静态变量,不用考虑加锁的问题。

每个线程都有消息队列吗?

  不是,知识创建了窗体对象的线程才会有消息队列(下面是《Windows核心编程》关于这一段的描述)

  当一个线程第一希被建立时,系统假定线程不会被用于任何与用户相关的任务。这样可以减少线程对系统资源的要求。但是,一旦这个线程调用一个与图形用户界面有关的函数(例如检查它的消息队列或建立一个窗口),系统就会为该线程分配一些另外的资源,以便它能够执行与用户界面有关的任务。特别是,系统分配一个THREADINFO结构,并将这个数据结构与线程联系起来。

  这个THREADINFO结构包含一组成员变量,利用这组成员,线程可以认为它是在自己独占的环境中运行。THREADINFO是一个内部的、未公开的数据结构,用来指定线程的登记消息队列(posted-message queue)、发送消息队列(send-message queue)、应答消息队列(reply-message queue)、虚拟输入队列(virtualized-input queue)、唤醒标志(wake flag)以及用来描述线程局部输入状态的若干变量。

为什么WinForm不允许跨线程修改UI线程控件的值

  vs2005及以上版本,当在Visual Studio调试器中运行代码时,如果您从一个线程访问某个UI元素,而该线程不是创建该UI元素时所在的线程,则会引发InvalidOperationException调试器引发该异常以警告您存在危险的编程操作。UI元素不是线程安全的,所以只应在创建它们的线程上进行访问。

有没有什么办法可以简化WinForm多线程的开发

  使用backgroundworker,使用这个组件可以避免回调时的Invoke和BeginInvoke,并且提供了许多丰富的方法和事件

线程池

线程池的作用是什么

  减小线程创建和销毁的开销

   创建线程涉及到用户模式和内核模式的切换,内存分配,dll通知等一系列过程,线程销毁的步骤也是开销很大的,所以如果应用程序使用完一个线程,我们能把线程暂时存放起来,以备下次使用,就可以减小这些开销。

所有进程使用一个共享的线程池,还是每个进程使用独立的线程池

  每个进程都有一个线程池,一个进程中只能有一个实例,它在各个应用程序域(AppDomain)是共享的,线程池仅仅保留相当少的线程,保留的线程可以用SetMinThread这个方法设置,当程序需要一个线程时,线程池中没有空闲的线程时,线程池就会负责创建这个线程,调用完后,不会立即销毁,而是把它放在池子里,以备下次使用,但是,如果超出一定时间没使用,线程池就会回收线程,所以线程池里存在的线程数实际是个动态的过程。

线程池中线程的分类

  线程池里的线程按照公用被分成了两大类:工作线程和IO线程(IO完成线程),前者用于执行普通操作,后者专用于异步IO。它们分别在什么情况下被使用,二者工作原理有什么不同?通过下面这个例子,我们用一个流读出一个很大的文件(文件大,操作时间长,便于观察),然后用另一个输出流把所读出的文件的一部分写到磁盘上。

  用两种方法创建输出流,分别是:

  创建一个异步的流(注意构造函数最后那个true)

  创建一个同步流

   string readPath = "d:\\工作常用软件\\VS2012Documentation.iso";
        string writePath = "d:\\vs2012.ios";
        byte[] buffer = new byte[90000000];
        //创建一个异步流
        FileStream outputfs = new FileStream(writePath, FileMode.Create, FileAccess.Write, FileShare.None, 256, true);
        Console.WriteLine("异步流");
        //创建一个同步流
        //FileStream outputfs = File.OpenWrite(writePath);
        //Console.WriteLine("同步流");
        //然后在写文件期间查看线程池的状况
        ShowThreadDetail("初始状态");
        FileStream fs = File.OpenRead(readPath);
        fs.BeginRead(buffer, 0, 90000000, delegate(IAsyncResult o)
        {
            outputfs.BeginWrite(buffer, 0, buffer.Length, delegate(IAsyncResult o1)
            {
                Thread.Sleep(1000);
                ShowThreadDetail("BeginWrite的回调线程");
            }, null);
            Thread.Sleep(500);
        },
        null);
        Console.ReadLine();

   public static void ShowThreadDetail(string caller)
        {
            int IO;
            int Worker;
            ThreadPool.GetAvailableThreads(out Worker, out IO);
            Console.WriteLine("Worker:{0};IO:{1}", Worker, IO);
        }

  输出结果

  异步流

  Worker:1023;    IO:1000

  Worker:1023;    IO:999

  同步流

  Worker:1023;    IO:1000

  Worker:1022;    IO:1000

  这两个构造函数创建的流都可以使用BeginWrite来异步写数据,但二者行为不同,当使用同步的流进行异步写时,通过回调的输出我们可以看到,它使用的是工作线程,而非IO线程,而异步流使用IO线程。

.NET线程池有什么不足

  没有提供方法控制加入线程池的线程:一旦加入线程池,我们没办法挂起,终止这些线程,唯一可以做的就是等他自己执行

  • 不能为线程设置优先级
  • 所支持的Callback不能有返回值。WaitCallback只能带一个object类型的参数
  • 不适合用于长期执行某任务的场合

同步

CLR怎样实现lock(obj)锁定

  从原理上讲,lock和Syncronized Attribute都是用Moniter.Enter实现的,例如:

  object obj = new object();

  lock(obj){

  //do things...

  }

  在编译时,会被编译为类似

  try{

    Moniter.Enter(obj){

    //do things...

    }

  }

  catch{...}

  finally{

    Moniter.Exit(obj);

  }

  每个对象实例头部都有一个指针,这个指针指向的结构包含了对象的锁定信息,当第一次使用Moniter.Enter(obj)是,这个obj对象的锁定结构就会被初始化,第二次调用时,会检验这个object的锁定结构,如果锁没有被释放,则调用会阻塞。

互斥对象(Mutex)、事件(Event)对象与lock语句的比较

  这里所谓的事件是一种用于同步的内核机制,互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模式间切换,所有一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个线程中的各个线程间进行同步。

  lock或者Moniter是.net用于一种特殊结构实现的,不涉及模式切换,就是工作在用户方式下,同步速度较快,但是不能跨进程同步。

C# 多线程 详解的更多相关文章

  1. iOS开发——多线程OC篇&多线程详解

    多线程详解 前面介绍了多线程的各种方式及其使用,这里补一点关于多线程的概念及相关技巧与使用,相信前面不懂的地方看了这里之后你就对多线程基本上没有什么问题了! 1——首先ios开发多线程中必须了解的概念 ...

  2. iOS开发——GCD多线程详解

    GCD多线程详解 1. 什么是GCD Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,简单来说,GCD就是iOS一套解决多线程的机制,使用GCD能够最大限度简化多线程 ...

  3. Java 多线程详解(四)------生产者和消费者

    Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程: ...

  4. java中多线程详解-synchronized

    一.介绍 当多个线程涉及到共享数据的时候,就会设计到线程安全的问题.非线程安全其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”.发生脏读,就是取到的数据已经被其他的线 ...

  5. python多线程详解

    目录 python多线程详解 一.线程介绍 什么是线程 为什么要使用多线程 二.线程实现 threading模块 自定义线程 守护线程 主线程等待子线程结束 多线程共享全局变量 互斥锁 递归锁 信号量 ...

  6. C#多线程详解(一) Thread.Join()的详解

    bicabo   C#多线程详解(一) Thread.Join()的详解 什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程 ...

  7. Java多线程详解(二)

    评论区留下邮箱可获得<Java多线程设计模式详解> 转载请指明来源 1)后台线程 后台线程是为其他线程服务的一种线程,像JVM的垃圾回收线程就是一种后台线程.后台线程总是等到非后台线程死亡 ...

  8. Delphi多线程详解

    (整理自网络) Delphi多线程处理 1-1多线程的基本概念 WIN 98/NT/2000/XP 是个多任务操作系统,也就是:一个进程可以划分为多个线程,每个线程轮流占用CPU 运行时间和资源,或者 ...

  9. Java中的多线程详解

    如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...

  10. php多线程详解

    在说明多线程的题前,需要弄清楚以下几个问题 1,ts 和 nts的区别 Thread Safe和NoneThread Safe 先说windows的,在php官网,在windows区域有在文件下在有 ...

随机推荐

  1. Echarts调整图表上下左右的间距,Echarts调整柱状图左右的间距

    Echarts调整图表上下左右的间距,Echarts调整柱状图左右的间距 >>>>>>>>>>>>>>>> ...

  2. RF判断列表、字典、整数、字符串类型是否相同方法

      ${d} create list shk shsh${w} create list ${e} evaluate type(${d}) ${t} evaluate type(${w}) should ...

  3. informix中的时间计算

    今天看SUN服务器是的mail(vi   /var/mail/xxxuser),发现定时任务上的一些存储过程执行有错误,其中有一个错误是long transaction,长事务错误,到数据库一查,天哪 ...

  4. Linux 日常运维

    查看用户信息:w 查看系统负载:uptime 查看系统资源使用情况:vmstat 查看进程动态:top 查看网卡流量:sar 查看网卡流量:nload 查看磁盘读写:iostat 查看磁盘读写:iot ...

  5. [Ubuntu] APT - Advanced Packaging Tool 简明指南

    Advanced Packaging Tool,一般简称为apt,是Debian GNU/Linux distribution及其变体版本中与核心库一道处理软件的安装和卸载. Ubuntu是Debia ...

  6. springboot 集成elasticsearch5.4.3

    官网上对elasticsearch 的集成用的是spring-data,而且,暂时不支持5.x的版本, 要是想集成5.x的版本,我们只能在pom.xml文件中进行修改,如图: <project ...

  7. STL——仿函数(函数对象)

    一.仿函数(也叫函数对象)概观 仿函数的作用主要在哪里?从第6章可以看出,STL所提供的各种算法,往往有两个版本,其中一个版本表现出最常用(或最直观)的某种运算,第二个版本则表现出最泛化的演算流程,允 ...

  8. 解决“”父级标签和子标签边框重叠,设置子标签的margin父标签会跟着移动“”的方法

    1.可以给父标签一个padding,然后给一个很小的值,虽然不影响整体但是不建议使用 2.给父标签一个"over:hidden"的样式,推荐使用

  9. 《Lua程序设计》第6章 深入函数 学习笔记

    在Lua中,函数是一种“第一类值(First-Class Value)”,它们具有特定的词法域(Lexical Scoping).“词法域”:函数可以潜逃在另一个函数中,内部的函数可以访问外部函数中的 ...

  10. 【开源整理】.Net开源项目资源大全

    汇总了.NET平台开源的工具类库,新的内容在不断更新中.内容借鉴了博客园.伯乐在线.GitHub等平台. (注:下面用 [$] 标注的表示收费工具,但部分收费工具针对开源软件的开发/部署/托管是免费的 ...