多线程之旅(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 ...
随机推荐
- 上海月薪 1w 和家乡月薪 5000 你选择哪?
如题,这是我在知乎上看到的一个热门话题--要现在的我来回答的话,毫无疑问会选择上海,即便月薪只有 5000 也去,还要趁早去. 有读者可能会质问我:"你之前不是说在三线城市洛阳工作很爽吗?怎 ...
- JavaScript规范----DOM操作
一般通过JS代码操作DOM结构,会触发浏览器进行页面渲染.所以要尽量减少DOM操作,避免频繁的页面渲染对性能造成影响. 如有以下场景:程序执行ajax请求,并根据请求结果动态添加列表项.常见的做法是循 ...
- Flutter Weekly Issue 47
教程 开辟 Dart 到 Native 的超级通道,饿了么跨平台的最佳实践 当永恒的软键盘问题遇到Flutter 插件 fijkplayer ijkplayer for flutter. ijkpla ...
- 在kubernetes1.17.2上结合ceph部署efk
简绍 应用程序和系统日志可以帮助我们了解集群内部的运行情况,日志对于我们调试问题和监视集群情况也是非常有用的.而且大部分的应用都会有日志记录,对于传统的应用大部分都会写入到本地的日志文件之中.对于容器 ...
- 《Java8 Stream编码实战》正式推出
当我第一次在项目代码中看到Stream流的时候,心里不由得骂了一句"傻X"炫什么技.当我开始尝试在代码中使用Stream时,不由得感叹真香. 记得以前有朋友聊天说,他在代码中用了 ...
- javaScript 基础知识汇总(七)
1.数组 特点:数组是可以存储有序集合的对象. 声明: let arr = new Array(); let arr=[]; 大多数情况下我们使用第二种. let fruits = [" ...
- 如何使用域名访问自己的Windows服务器(Java web 项目)
如何使用域名访问自己的Windows服务器(Java web 项目) 写在前面 前段时间在阿里云弄了个学生服务器,就想着自己搭建一个网站试一试,在网上查阅相关资料时发现大部分都是基于服务器是Linux ...
- NIO中的ZeroCopy
前文提到网络IO可以使用多路复用技术,而文件IO无法使用多路复用,但是文件IO可以通过减少底层数据拷贝的次数来提升性能,而这个减少底层数据拷贝次数的技术,就叫做ZeroCopy. 操作系统层面的Zer ...
- 性能测试工具LoadRuner你所不知道的内幕
谈到性能测试,大家一定会联想到Jmeter和LoadRunner,这两款工具目前在国内使用的相当广泛,主要原因是Jmeter是开源免费,LoadRunner 11在现网中存在破解版本.商用型性能测试工 ...
- Redis详解(一)
redis简介 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多 包括string(字符串).list(链表).set(集合).zset(sor ...