多线程之旅(Task 任务)
一、Task(任务)和ThreadPool(线程池)不同
1、线程(Thread)是创建并发工具的底层类,但是在前几篇文章中我们介绍了Thread的特点,和实例。可以很明显发现局限性(返回值不好获取(必须在一个作用域中)),当我们线程执行完之后不能很好的进行下一次任务的执行,需要多次销毁和创建,所以不是很容易使用在多并发的情况下。
2、线程池(ThreadPool) QueueUserWorkItem是很容易发起并发任务,也解决了上面我们的需要多次创建、销毁的性能损耗解决了,但是我们就是太简单的,我不知道线程什么时候结束,也没有获取返回值的途径,也是比较尴尬的事情。
3、任务(Task)表示一个通过或不通过线程实现的并发操作,任务是可组合的,使用延续(continuation)可将它们串联在一起,它们可以使用线程池减少启动延迟,可使用回调方法避免多个线程同时等待I/O密集操作。
二、初识Task(任务)
1、Task(任务)是在.NET 4.0引入的、Task是在我们线程池ThreadPool上面进行进一步的优化,所以Task默认还是线程池线程,并且是后台线程,当我们的主线程结束时其他线程也会结束
2、Task创建任务,也和之前差不多
/// <summary>
/// Task 的使用
/// Task 的创建还是差不多的
/// </summary>
public static void Show()
{
//实例方式
Task task = new Task(() =>
{
Console.WriteLine("无返回参数的委托");
}); //无参有返回值
Task<string> task1 = new Task<string>(() =>
{
return "我是返回值";
}); //有参有返回值
Task<string> task2 = new Task<string>(x =>
{
return "返回值 -- " + x.ToString();
}, "我是输入参数");
//开启线程
task2.Start();
//获取返回值 Result会堵塞线程获取返回值
Console.WriteLine(task2.Result); //使用线程工厂创建 无参数无返回值线程
Task.Factory.StartNew(() =>
{
Console.WriteLine("这个是线程工厂创建");
}).Start(); //使用线程工厂创建 有参数有返回值线程
Task.Factory.StartNew(x =>
{
return "返回值 -- " + x.ToString(); ;
}, "我是参数"); //直接静态方法运行
Task.Run(() =>
{
Console.WriteLine("无返回参数的委托");
});
}
说明:
1、事实上Task.Factory类型本身就是TaskFactory(任务工厂),而Task.Run(在.NET4.5引入,4.0版本调用的是后者)是Task.Factory.StartNew的简写法,是后者的重载版本,更灵活简单些。
2、调用静态Run方法会自动创建Task对象并立即调用Start
3、Task.Run等方式启动任务并没有调用Start,因为它创建的是“热”任务,相反“冷”任务的创建是通过Task构造函数。
三、Task(任务进阶)
1、Wait 等待Task线程完成才会执行后续动作
//创建一个线程使用Wait堵塞线程
Task.Run(() =>
{
Console.WriteLine("Wait 等待Task线程完成才会执行后续动作");
}).Wait();
2、WaitAll 等待Task[] 线程数组全部执行成功之后才会执行后续动作
//创建一个装载线程的容器
List<Task> list = new List<Task>();
for (int i = ; i < ; i++)
{
list.Add(Task.Run(() =>
{
Console.WriteLine("WaitAll 执行");
}));
}
Task.WaitAll(list.ToArray());
Console.WriteLine("Wait执行完毕");
3、WaitAny 等待Task[] 线程数组任一执行成功之后就会执行后续动作
//创建一个装载线程的容器
List<Task> list = new List<Task>();
for (int i = ; i < ; i++)
{
list.Add(Task.Run(() =>
{
Console.WriteLine("WaitAny 执行");
}));
}
Task.WaitAny(list.ToArray());
Console.WriteLine("WaitAny 执行完毕");
4、WhenAll 等待Task[] 线程数组全部执行成功之后才会执行后续动作、与WaitAll不同的是他有回调函数ContinueWith
//创建一个装载线程的容器
List<Task> list = new List<Task>();
for (int i = ; i < ; i++)
{
list.Add(Task.Run(() =>
{
Console.WriteLine("WhenAll 执行");
}));
}
Task.WhenAll(list.ToArray()).ContinueWith(x =>
{
return x.AsyncState;
});
Console.WriteLine("WhenAll 执行完毕");
5、WhenAny 等待Task[] 线程数组任一执行成功之后就会执行后续动作、与WaitAny不同的是他有回调函数ContinueWith
//创建一个装载线程的容器
List<Task> list = new List<Task>();
for (int i = ; i < ; i++)
{
list.Add(Task.Run(() =>
{
Console.WriteLine("WhenAny 执行");
}));
}
Task.WhenAny(list.ToArray()).ContinueWith(x =>
{
return x.AsyncState;
});
Console.WriteLine("WhenAny 执行完毕");
Console.ReadLine();
四、Parallel 并发控制
1、是在Task的基础上做了封装 4.5,使用起来比较简单,如果我们执行100个任务,只能用到10个线程我们就可以使用Parallel并发控制
public static void Show5()
{
//第一种方法是
Parallel.Invoke(() =>
{
Console.WriteLine("我是线程一号");
}, () =>
{
Console.WriteLine("我是线程二号");
}, () =>
{
Console.WriteLine("我是线程三号");
}); //for 方式创建多线程
Parallel.For(, , x =>
{
Console.WriteLine("这个看名字就知道是for了哈哈 i=" + x);
}); //ForEach 方式创建多线程
Parallel.ForEach(new string[] { "", "", "", "", "" }, x => Console.WriteLine("这个看名字就知道是ForEach了哈哈 i=" + x)); //这个我们包一层,就不会卡主界面了
Task.Run(() =>
{
//创建线程选项
ParallelOptions parallelOptions = new ParallelOptions()
{
MaxDegreeOfParallelism =
};
//创建一个并发线程
Parallel.For(, , parallelOptions, x =>
{
Console.WriteLine("限制执行的次数");
});
}).Wait();
Console.WriteLine("**************************************"); //Break Stop 都不推荐用
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = ;
Parallel.For(, , parallelOptions, (i, state) =>
{
if (i == )
{
Console.WriteLine("线程Break,Parallel结束");
state.Break();//结束Parallel
//return;//必须带上
}
if (i == )
{
Console.WriteLine("线程Stop,当前任务结束");
state.Stop();//当前这次结束
//return;//必须带上
}
Console.WriteLine("我是线程i=" + i);
});
}
五、多线程实例
1、代码异常我信息大家都不陌生,比如我刚刚写代码经常会报 =>对象未定义null 的真的是让我心痛了一地,那我们的多线程中怎么去处理代码异常呢? 和我们经常写的同步方法不一样,同步方法遇到错误会直接抛出,当是如果我们的多线程中出现代码异常,那么这个异常会自动传递调用Wait 或者 Task<TResult> 的Result属性上面。任务的异常会将自动捕获并且抛给调用者,为了确保报告所有的异常,CLR会将异常封装到AggregateExcepiton容器中,这容器是公开了InnerExceptions属性中包含所有捕获的异常,但是如果我们的线程没有等待结束不会获取到异常。
class Program
{
static void Main(string[] args)
{
try
{
Task.Run(() =>
{
throw new Exception("错误");
}).Wait();
}
catch (AggregateException axe)
{
foreach (var item in axe.InnerExceptions)
{
Console.WriteLine(item.Message);
}
}
Console.ReadKey();
}
}
/// <summary>
/// 多线程捕获异常
/// 多线程会将我们的异常吞了,因为我们的线程执行会直接执行完代码,不会去等待你捕获到我的异常。
/// 我们的线程中最好是不要出现异常,自己处理好。
/// </summary>
public static void Show()
{
//创建一个多线程工厂
TaskFactory taskFactory = new TaskFactory();
//创建一个多线程容器
List<Task> tasks = new List<Task>();
//创建委托
Action action = () =>
{
try
{
string str = "sad";
int num = int.Parse(str);
}
catch (AggregateException ax)
{
Console.WriteLine("我是AggregateException 我抓到了异常啦 ax:" + ax);
}
catch (Exception)
{
Console.WriteLine("我是线程我已经报错了");
}
};
//这个是我们经常需要做的捕获异常
try
{
//创建10个多线程
for (int i = ; i < ; i++)
{
tasks.Add(taskFactory.StartNew(action));
}
Task.WaitAll(tasks.ToArray());
}
catch (Exception ex)
{
Console.WriteLine("异常啦");
}
Console.WriteLine("我已经执行完了");
}
2、多线程取消机制,我们的Task在外部无法进行暂停 Thread().Abort() 无法很好控制,上上篇中Thread我们也讲到了Thread().Abort() 的不足之处。有问题就有解决方案。如果我们使用一个全局的变量控制,就需要不断的监控我们的变量取消线程。那么说当然有对应的方法啦。CancellationTokenSource (取消标记源)我们可以创建一个取消标记源,我们在创建线程的时候传入我们取消标记源Token。Cancel()方法 取消线程,IsCancellationRequested 返回一个bool值,判断是不是取消了线程了。
/// <summary>
/// 多线程取消机制 我们的Task在外部无法进行暂停 Thread().Abort() 无法很好控制,我们的线程。
/// 如果我们使用一个全局的变量控制,就需要不断的监控我们的变量取消线程。
/// 我们可以创建一个取消标记源,我们在创建线程的时候传入我们取消标记源Token
/// Cancel() 取消线程,IsCancellationRequested 返回一个bool值,判断是不是取消了线程了
/// </summary>
public static void Show1()
{
//创建一个取消标记源
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
//创建一个多线程工厂
TaskFactory taskFactory = new TaskFactory();
//创建一个多线程容器
List<Task> tasks = new List<Task>();
//创建委托
Action<object> action = x =>
{
try
{
//每个线程我等待2秒钟,不然
Thread.Sleep();
//判断是不是取消线程了
if (cancellationTokenSource.IsCancellationRequested)
{
Console.WriteLine("放弃执行后面线程");
return;
}
if (Convert.ToUInt32(x) == )
{
throw new Exception(string.Format("{0} 执行失败", x));
}
Console.WriteLine("我是正常的我在执行");
}
catch (AggregateException ax)
{
Console.WriteLine("我是AggregateException 我抓到了异常啦 ax:" + ax);
}
catch (Exception ex)
{
//异常出现取消后面执行的所有线程
cancellationTokenSource.Cancel();
Console.WriteLine("我是线程我已经报错了");
}
};
//这个是我们经常需要做的捕获异常
try
{
//创建10个多线程
for (int i = ; i < ; i++)
{
int k = i;
tasks.Add(taskFactory.StartNew(action, k, cancellationTokenSource.Token));
}
Task.WaitAll(tasks.ToArray());
}
catch (Exception ex)
{
Console.WriteLine("异常啦");
}
Console.WriteLine("我已经执行完了");
}
3、多线程创建临时变量,当我们启动线程之后他们执行没有先后快慢之分,正常的循环中的变量也没有作用。这个时候就要创建一个临时变量存储信息,解决不访问一个数据源。
/// <summary>
/// 线程临时变量
/// </summary>
public static void Show2()
{
//创建一个线程工厂
TaskFactory taskFactory = new TaskFactory();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
//创建一个委托
Action<object> action = x =>
{
Console.WriteLine("传入参数 x:" + x);
};
for (int i = ; i < ; i++)
{
//这最主要的就是会创建20个k的临时变量
int k = i;
taskFactory.StartNew(action, k);
}
Console.ReadLine();
}
4、多线程锁,之前我们有提到过我们的多线程可以同时公共资源,如果我们有个变量需要加一,但是和这个时候我们有10个线程同时操作这个会怎么样呢?
public static List<int> list = new List<int>();
public static int count = ; public static void Show3()
{
//创建线程容器
List<Task> tasks = new List<Task>();
for (int i = ; i < ; i++)
{
//添加线程
tasks.Add(Task.Run(() =>
{
list.Add(i);
count++;
}));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine("list 行数:" + list.Count + " count 总数:" + count);
Console.ReadLine();
}
我们上面的代码本来是count++到10000,但是我们看到结果的时候,我们是不是傻了呀,怎么是不是说好的10000呢,其实的数据让狗吃了?真的是小朋友有很多问号??????
5、那么我们要怎么去解决这个问题呢?方法还是有的今天我们要将到一个语法糖lock、它能做什么呢?它相当于一个代码块锁,它主要锁的是一个对象,当它锁住对象的时候会当其他线程发生堵塞,因为当它锁住代码时候也是锁住了对象的访问链,是其他的线程不能访问。必须等待对象访问链被释放之后才能被一个线程访问。我们的使用lock锁代码块的时候,尽量减少锁入代码块范围,因为我们锁代码之后会导致只有一个线程可以拿到数据,尽量只要必须使用lock的地方使用。
6、Lock使用要注意的地方
1、lock只能锁引用类型的对象.
2、不能锁空对象null某一对象可以指向Null,但Null是不需要被释放的。(请参考:认识全面的null)。
3、lock 尽量不要去锁string 类型虽然它是引用类型,但是string是享元模式,字符串类型被CLR“暂留”
这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。
4、lock就避免锁定public 类型或不受程序控制的对象。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。
/// <summary>
/// 创建一个静态对象,主要是用于锁代码块,如果是静态的就会全局锁,如果要锁实例类,就不使用静态就好了
/// </summary>
private readonly static object obj = new object();
public static List<int> list = new List<int>();
public static int count = ;
/// <summary>
/// lock 多线程锁
/// 当我们的线程访问同一个全局变量、同时访问同一个局部变量、同一个文件夹,就会出现线程不安全
/// 我们的使用lock锁代码块的时候,尽量减少锁入代码块范围,因为我们锁代码之后会导致只有一个线程可以
/// 访问到我们代码块了
/// </summary>
public static void Show3()
{
//创建线程容器
List<Task> tasks = new List<Task>();
//锁代码
for (int i = ; i < ; i++)
{
//添加线程
tasks.Add(Task.Run(() =>
{
//锁代码
lock (obj)
{
//这个里面就只会出现一个线程访问,资源。
list.Add(i);
count++;
}
//lock 是一个语法糖,就是下面的代码
Monitor.Enter(obj); Monitor.Exit(obj);
}));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine("list 行数:" + list.Count + " count 总数:" + count);
Console.ReadLine();
}
7、总结实例篇,双色球实例。
1、双色球:投注号码由6个红色球号码和1个蓝色球号码组成。红色球号码从01--33中选择(不重复)蓝色球号码从01--16中选择(可以跟红球重复),代码我已经实现了大家可以下载源码。只有自己多多倒腾才能让自己的技术成长。 下一次我们async和await这两个关键字下篇记录
多线程之旅(Task 任务)的更多相关文章
- C#多线程之旅(3)——线程池
v博客前言 先交代下背景,写<C#多线程之旅>这个系列文章主要是因为以下几个原因:1.多线程在C/S和B/S架构中用得是非常多的;2.而且多线程的使用是非常复杂的,如果没有用好,容易造成很 ...
- C#多线程之旅(4)——APM初探
源码地址:https://github.com/Jackson0714/Threads 原文地址:C#多线程之旅(4)——APM初探 C#多线程之旅目录: C#多线程之旅(1)——介绍和基本概念 C# ...
- C#多线程之旅(1)——介绍和基本概念
原文地址:C#多线程之旅(1)——介绍和基本概念 C#多线程之旅目录: C#多线程之旅(1)——介绍和基本概念 C#多线程之旅(2)——创建和开始线程 C#多线程之旅(3)——线程池 C#多线程之旅( ...
- 重新想象 Windows 8 Store Apps (43) - 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel)
[源码下载] 重新想象 Windows 8 Store Apps (43) - 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel) 作者:webabcd 介绍重新想象 W ...
- C#多线程实现方法——Task/Task.Factary
原文:C#多线程实现方法--Task/Task.Factary Task 使用 Task以及Task.Factory都是在.Net 4引用的.Task跟Thread很类似,通过下面例子可以看到. st ...
- C#多线程之旅(7)——终止线程
先交代下背景,写<C#多线程之旅>这个系列文章主要是因为以下几个原因:1.多线程在C/S和B/S架构中用得是非常多的;2.而且多线程的使用是非常复杂的,如果没有用好,容易造成很多问题. ...
- .net 多线程 Thread ThreadPool Task
先准备一个耗时方法 /// <summary>/// 耗时方法/// </summary>/// <param name="name">< ...
- c#中@标志的作用 C#通过序列化实现深表复制 细说并发编程-TPL 大数据量下DataTable To List效率对比 【转载】C#工具类:实现文件操作File的工具类 异步多线程 Async .net 多线程 Thread ThreadPool Task .Net 反射学习
c#中@标志的作用 参考微软官方文档-特殊字符@,地址 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/toke ...
- 转载 .Net多线程编程—任务Task https://www.cnblogs.com/hdwgxz/p/6258014.html
.Net多线程编程—任务Task 1 System.Threading.Tasks.Task简介 一个Task表示一个异步操作,Task的创建和执行是独立的. 只读属性: 返回值 名称 说明 ob ...
随机推荐
- openwrt sdk 编译工具 及 hello world
需要先在 make menuconfig 中打开 sdk make -j4在 bin\targets\ramips\mt7620生成一个openwrt-sdk-ramips-mt7620_gcc-7. ...
- 树莓派3B+远程VNC的设置
现在很少有自带VNCserver的教程,因为之前官方系统没有自带VNC,但是现在最新版的官方系统已经自带VNCserver,只需要在设置里启用一下,然后设置就可以用啦. 1.打开树莓派设置 sudo ...
- 从火箭发场景来学习Java多线程并发闭锁对象
从火箭发场景来学习Java多线程并发闭锁对象 倒计时器场景 在我们开发过程中,有时候会使用到倒计时计数器.最简单的是:int size = 5; 执行后,size—这种方式来实现.但是在多线程并发的情 ...
- 【分布式锁】05-使用Redisson中Semaphore和CountDownLatch原理
前言 前面已经写了Redisson大多的内容,我们再看看Redisson官网共有哪些组件: image.png 剩下还有Semaphore和CountDownLatch两块,我们就趁热打铁,赶紧看看R ...
- .NetCore 3.1 MVC 发布Linux实操
记录自己.net Core的学习,现在来发布到Linux系统 第一步:准备一台CentOS 7 X64服务器 .net core 3.1项目 第二步:准备服务器.net core环境 1.安装微软官方 ...
- ERP系统功能及部署方式
ERP是对企业经营过程中的一些事项进行系统化管理的一种软件,所以ERP软件包含企业经营过程中需要用到的多种功能管理模块,并且ERP软件有两种部署方式.下面一起来了解一下相关的知识吧! ERP系统的功能 ...
- 贵州省网络安全知识竞赛团体赛Writeup-phpweb部分
0x01 混淆后门#conn.php 首先还是拖到D盾扫描 打开conn.php发现底部有那么一串代码: 对这个代码进行分析 首先可以对几个比较简单的变量输出看一下 $s输出内容为create_fun ...
- Postman-OAuth 2.0授权
一.Postman提供的授权类型有10种.授权过程将验证是否有权从服务器访问所需的数据.发送请求时,通常必须包含参数以确保请求有权访问并返回所需的数据. 二.使用第7种OAuth 2.0授权:OAut ...
- 第十周Java实验作业
实验十 泛型程序设计技术 实验时间 2018-11-1 1.实验目的与要求 (1) 理解泛型概念: 泛型:也称参数化类型,就是在定义类,接口和方法时,通过类型参数只是将要处理的类型对象.(如Arra ...
- 动态规划-买卖股票的最佳时机 V
2020-03-11 18:19:00 问题描述: 给出一个股票n天的价格,每天最多只能进行一次交易,可以选择买入一支股票或卖出一支股票或放弃交易,输出能够达到的最大利润值 样例 样例 1: 给出 ` ...