在开发中经常有遇到因为程序执行的时间过长,而造成程序假死的情况,这是因为我们的程序是同步执行的,当执行到需要长时间的操作时,程序就会等待当前的操作完成,从而造成程序假死。C#的异步与多线程就是为了解决这个问题的。

  1. 什么是多线程,举个简单的例子,我们在做饭的时候,可以先煮好饭,然后炒菜,然后洗餐具,然后完成,每一个操作都是在前一个操作完成之后才能进行,这就叫做同步执行,我们也可以在边煮饭的同时炒菜,洗餐具,当所有的工作都做完的时候,饭也就做好了,在这个过程中,煮饭,炒菜是同时进行的,这个就是异步,多线程就类似于主线程在煮饭,然后另开一个新的线程用来炒菜。
  2. 多线程的优缺点,由上所述,多线程可以在新开的线程中执行操作,所以他不需要等到主线程完成,也不收主线程的影响,自然,也就不会造成程序假死的情况出现了。这样可以提高用户的交互体验,防止用户以为程序崩溃也不断重启。但是由于我们直接感受到的是主线程,而新开的线程其实是在后台执行的,所以,如果新开的线程出现了异常,我们很难再主线程中捕获,或者处理,同时新开一个线程也需要计算机硬件的支持,如果线程过多,可能会造成系统变得很卡,资源消费过多的现象。
  3. 什么情况下需要使用多线程的技术?根据多线程的特点,其实类似于一个后台执行的任务,所以一般在以下情况中会使用多线程技术。1、程序执行时间比较长,同时程序的结果不是那么重要,不应该是主线程等待结果的情况下,可以使用多线程异步执行。例如,登录之后的验证过程。2、需要定时刷新的功能,这些功能是定时循环执行,所以可以放在后台去异步执行,这样既能保证功能执行了,同时在执行的过程中也不会造成程序卡顿的现象。3、后台任务,程序只需要执行相关的功能,不需要接收执行结果。例如发送一条短信,我们只需要发送出去即可,不需要知道用户是否接收到了短信,这样的情况下可以使用异步发送。其他情况可以参考以上的情况来决定是否需要使用多线程。

多线程编程示例

.Net提供了多种方式实现多线程的编程,包括线程池,Thread,Task等方法,下面对应这些方法给出简单的示例。

首先,建立一个功能类,此类的作用是多线程需要执行的方法,方法包括,无参数无返回值,有参数无返回值,无参数有返回值,有参数有返回值四种情况。

/// <summary>

/// 此类用于多线程测试的公用方法的定义

/// </summary>

public class CommonClass

{

//注意多线程使用的方法对应的参数类型应该为object

/// <summary>

/// 无返回值有参数的方法,用于无返回值的多线程的类型的使用

/// </summary>

/// <param name="name"></param>

public void ShowName(object name)

{

Console.WriteLine("Your Name Is: {0}", name);

}

/// <summary>

/// 有返回值的方法,用于有返回值的多线程类型的使用

/// </summary>

/// <param name="name"></param>

/// <returns></returns>

public string GetName(object name)

{

return name + "English's name is Lily";

}

}

上面的代码只有两个方法,都带参数,但是一个有返回值,一个没有返回值。对于无参数的方法,只需要把传入的参数设置为null即可。

  1. 使用ThreadPool实现多线程

ThreadPool顾名思义就是线程池,由于创建一个新的线程的代价比较大,所以如果没有必要,就不需要创建一个新的线程。线程池就是为此设计的,当新创建一个线程的时候,首先到线程池中查询是否有空闲线程,如有,则直接使用此线程,这样就避免了新创建一个线程,若无,则新创建一个线程,并加入到线程池中,当线程执行结束之后,当前线程变为空闲线程,并放入线程池,等待下一个调动。当创建的线程数超过线程池允许的最大线程数之后,线程就需要排队,等待空闲线程的出现。

/// <summary>

/// 线程池实现多线程的定义

/// 线程池与Thread的区别在于,Thread每次都是新建一个线程,执行完成后就销毁

/// 而线程池有一个最大线程数,会在池内空闲线程数不够的时候创建新的线程,并存入

/// 线程池,线程执行完成后并不会销毁,而是存入线程池,作为空闲线程,等待下一次调用,

/// 超过线程池最大线程数时,不会再创建新的线程,而是排队等待新的空闲线程,因此,

/// 线程池比Thread的性能更好

/// </summary>

public class ThreadPoolTest

{

//和Thread一样,线程池只能创建无返回值和最多带一个参数的多线程方法

public void CreateThread()

{

//ThreadPool.SetMaxThreads(10, 10);//设置线程池最大线程数的方法

//ThreadPool.SetMinThreads(5, 5);//设置线程池最小线程数的方法

var com = new CommonClass();

for (int i = 2; i < 7; i++)

{

//往线程池中添加5个方法,但是线程池中不一定会创建5个线程

ThreadPool.QueueUserWorkItem(new WaitCallback(com.ShowName), i.ToString());

}

}

}

线程池的方法可以有参数,但是不能有返回值,或者只能返回void。

  1. Thread类实现多线程

Thread用于创建一个线程,他与线程池的区别就在于,他每次都会新创建一个线程,执行完成之后销毁线程。不存在等待空闲线程的概念,性能取决于硬件设备的性能。

/// <summary>

/// 此类用于多线程的Thread类实现测试

/// </summary>

public class ThreadTest

{

//Thread不能创建带有返回值的多线程方法,如果需要请使用Task

/// <summary>

/// 用于创建多线程并行的代码

/// </summary>

public void CreateThread()

{

var com = new CommonClass();

for (int i = 0; i < 5; i++)

{

//ParameterizedThreadStart类型用于定义一个带参数的方法的线程,如果需要定义不带参数的多线程实现,请使用ThreadStart

//同时参数只能有一个,如果需要使用多个参数,请使用其他多线程实现方法,或将多个参数直接封装为一个Class进行传递

Thread t = new Thread(new ParameterizedThreadStart(com.ShowName));

t.Start(i.ToString());

}

}

}

上面代码中红字部分为要执行的方法,从字面意思即可知道,这个方法是需要定义参数的,如果需要定义无参数的方法,则需要使用new ThreadStart(方法名),t.Start()方法用于开始执行线程,方法的参数即为调用的方法需要传入的参数。在上面的示例中,传入的参数即为ShowName方法所需的参数。和ThreadPool一样,Thread也是不能有返回值的。

  1. Task实现多线程

Task即为任务,也是实现多线程的一种方式,他定义的方法必须带一个object的参数,同时可以有返回值。

/// <summary>

/// 通过Task实现多线程的方法

/// Task相比Thread和ThreadPool的区别最直观的就在于可以实现带返回值的方法

/// </summary>

public class TaskTest

{

/// <summary>

/// 使用Task创建不带返回值的多线程

/// 此处的void也可以为Task,在async的异步编程中必须为Task

/// 具体实现请参考CreateReturTaskThread

/// </summary>

public void CreateNoReturnThread()

{

for (int i = 0; i < 5; i++)

{

//此处会形成一个闭包,所以多次返回的结果一样

Task.Run(() =>

{

Console.WriteLine("This Number is {0}", i);

});

//可以通过如下委托的方式解决上面的问题

//Action<int> act = (a) =>

//{

//    Task.Run(() =>

//    {

//        Console.WriteLine("This Number is {0}", a);

//    });

//};

//act(i);

}

}

/// <summary>

/// 返回Task的方法

/// </summary>

/// <returns></returns>

public Task CreateReturTaskThread()

{

return Task.Run(() =>

{

Console.WriteLine("This is a Method for return Task!");

});

}

/// <summary>

/// 创建返回具体值的方法

/// </summary>

/// <returns></returns>

public Task<string> CreateReturnNameThread()

{

return Task.Run(() =>

{

CommonClass com = new CommonClass();

string name = com.GetName("HoS ");

Thread.Sleep(5000);

return name;

});

}

}

在上面的示例中,既有返回void的方法,也有返回具体值的方法,使用Task.Run方法即可定义一个异步执行的方法。Run内部需要传入一个委托,来定义需要异步执行的功能,相比较Thread,代码的是想相对复杂,但是可以有返回值,同时创建一个任务相比建创建一个线程的开销小很多。

  1. async与await关键字

在C#4.0以后为简化异步操作,添加可async与await两个关键字,这两个关键字的内部也是通过Task来实现异步操作,但是如果不添加这两个关键字,那么方法就会以同步执行的方式来执行。同时await会等待方法执行的结果,但是在等待的过程中,不会阻塞主线程,也就不会造成程序假死的现象。

/// <summary>

/// 此类用于测试.Net4.0的async实现异步编程的方法

/// </summary>

public class AsyncTest

{

//1.定义一个返回值为Task<T>的方法

public Task<int> GetSum(List<int> list)

{

return Task.Run(() =>

{

return list.Sum();

});

}

//2.定义一个标识了async的方法

/// <summary>

/// 此方法用async标识,代表这是一个异步执行的方法,此方法不会阻塞当前线程

/// </summary>

public async void ShowSum()

{

List<int> list = new List<int>{ 5, 15, 12, 7, 9, 13, 6, 21 };

//此代码加了await标识,与async配合使用,代表这是一个异步的方法,

//如果不加await方法,则此处代码会同步执行

//需要注意的是await后面的方法需要等到await执行完成之后才会继续执行,

//而不是和后面的代码一起执行,也就是说这里实现了间接的多线程的同步的功能,

//同时不会卡住主线程,可以解决Winform项目中界面的假死的问题

//因为使用了await关键字,所以不需要在使用.Result来获取Task的结果了

int result = await GetSum(list);

Console.WriteLine("Result is {0}",result);

}

}

如上代码所示async是需要添加在方法的定义上面,同时微软建议,所有的async标识的方法返回的都应该是Task<T>如果是返回void则返回Task,上面的方法仅做演示所以未遵照此要求,需要注意。await关键字放在需要异步执行的方法之前即可。

以上就是几种实现多线程的方式。调用的方法也很简单

//注意:多线程由于是异步执行,所以可能造成无法预知的执行顺序,即以下代码每次执行的结果都可能不同

//1.Thread实现

//ThreadTest.Common.ThreadTest tt = new Common.ThreadTest();

//tt.CreateThread();

//2.ThreadPool实现

//ThreadPoolTest tpt = new ThreadPoolTest();

//tpt.CreateThread();

//3.Task实现

//3.1 无返回值的实现

//TaskTest tt = new TaskTest();

//tt.CreateNoReturnThread();

//3.2 返回一个Task的实现

//var result = tt.CreateReturTaskThread();

//3.3 返回一个Task<T>的实现

//var result = tt.CreateReturnNameThread();

//Console.WriteLine(result.Result);

//4.async的实现

//var at = new AsyncTest();

//at.ShowSum();

//5.多线程的同步实现

//5.1 AutoResetEventTest

//LockTest lt = new LockTest();

//lt.AutoResetEventTest();

//5.2 MutexTest

//lt.MutexTest();

//5.3 MulLock

//lt.MulLock();

//5.3  ManualResetEvent实现线程的手动挂起

//lt.MulLockM();

AsyncTest at = new AsyncTest();

at.ShowSumNo();

Console.ReadKey();

上面的代码仅供学习使用,如果需要更加深入的了解各种方式,请参考相关的教程,谢谢!

C#多线程编程笔记的更多相关文章

  1. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

  2. linux 多线程编程笔记

    一, 线程基础知识 1,线程的概念 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行 中必不可少的资源(如程序计 ...

  3. 【C】多线程编程笔记

    1. pthread_create(pthread类型指针变量 ,NULL ,函数 ,函数参数[多个参数用结构体传]) 2. pthread_join(pthread类型指针变量, 返回一般为null ...

  4. Java基础复习笔记系列 八 多线程编程

    Java基础复习笔记系列之 多线程编程 参考地址: http://blog.csdn.net/xuweilinjijis/article/details/8878649 今天的故事,让我们从上面这个图 ...

  5. 孙鑫VC学习笔记:多线程编程

    孙鑫VC学习笔记:多线程编程 SkySeraph Dec 11st 2010  HQU Email:zgzhaobo@gmail.com    QQ:452728574 Latest Modified ...

  6. 多线程编程学习笔记——async和await(一)

    接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...

  7. 多线程编程学习笔记——async和await(二)

    接上文 多线程编程学习笔记——async和await(一) 三.   对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...

  8. 多线程编程学习笔记——async和await(三)

    接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...

  9. 多线程编程学习笔记——使用异步IO(一)

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

随机推荐

  1. group by 多个字段

    众所周知,group by 一个字段是根据这个字段进行分组,那么group by 多个字段的结果是什么呢?由前面的结论类比可以得到,group by 后跟多个子段就是根据多个字段进行分组 注:下面的例 ...

  2. C# CreateParams的使用(解决闪屏问题)

    <转载自:https://blog.csdn.net/xpwang/article/details/53427479> 窗体和控件的属性CreateParams(这真的是一个属性)很神奇, ...

  3. sublime No packages available for installation

    package control user-setting 中添加: "channels": [ "C:\\channel_v3.json" ], channel ...

  4. 实验五:任意输入10个int类型数据,排序输出,并找出素数

    源代码: package 数组;import java.util.*;public class vvv { public static void main(String[] args) { Scann ...

  5. SSIS - 11.For循环容器

    一.For循环容器中的3个循环变量 For循环容器,类似于编程语言中的For,用于重复执行容器内的任务,直到条件返回为False.与编程语言类似,For循环容器也需要定义以下3种循环属性: 注: 必须 ...

  6. js 中 的 BOM对象

    BOM对象(浏览器对象模型 Browser Object Model) 01.页面的前进和后退 02.移动,调整和关闭浏览器窗口 03.创建新的浏览器窗口 01.window对象 ***** 核心对象 ...

  7. java前后分离使用fetch上传文件失败500

    这次不是写什么技术要点,仅仅是记录一下 最近遇到的一个问题 背景 使用fetch向java后台上传文件时,前端调试报错500,后端的报错为multipart 无法解析,翻译过来大概是这个意思. 由于本 ...

  8. [Swift]LeetCode108. 将有序数组转换为二叉搜索树 | Convert Sorted Array to Binary Search Tree

    Given an array where elements are sorted in ascending order, convert it to a height balanced BST. Fo ...

  9. [Swift]LeetCode339. 嵌套链表权重和 $ Nested List Weight Sum

    Given a nested list of integers, return the sum of all integers in the list weighted by their depth. ...

  10. [Swift]LeetCode468. 验证IP地址 | Validate IP Address

    Write a function to check whether an input string is a valid IPv4 address or IPv6 address or neither ...