一、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 任务)的更多相关文章

  1. C#多线程之旅(3)——线程池

    v博客前言 先交代下背景,写<C#多线程之旅>这个系列文章主要是因为以下几个原因:1.多线程在C/S和B/S架构中用得是非常多的;2.而且多线程的使用是非常复杂的,如果没有用好,容易造成很 ...

  2. C#多线程之旅(4)——APM初探

    源码地址:https://github.com/Jackson0714/Threads 原文地址:C#多线程之旅(4)——APM初探 C#多线程之旅目录: C#多线程之旅(1)——介绍和基本概念 C# ...

  3. C#多线程之旅(1)——介绍和基本概念

    原文地址:C#多线程之旅(1)——介绍和基本概念 C#多线程之旅目录: C#多线程之旅(1)——介绍和基本概念 C#多线程之旅(2)——创建和开始线程 C#多线程之旅(3)——线程池 C#多线程之旅( ...

  4. 重新想象 Windows 8 Store Apps (43) - 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel)

    [源码下载] 重新想象 Windows 8 Store Apps (43) - 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel) 作者:webabcd 介绍重新想象 W ...

  5. C#多线程实现方法——Task/Task.Factary

    原文:C#多线程实现方法--Task/Task.Factary Task 使用 Task以及Task.Factory都是在.Net 4引用的.Task跟Thread很类似,通过下面例子可以看到. st ...

  6. C#多线程之旅(7)——终止线程

    先交代下背景,写<C#多线程之旅>这个系列文章主要是因为以下几个原因:1.多线程在C/S和B/S架构中用得是非常多的;2.而且多线程的使用是非常复杂的,如果没有用好,容易造成很多问题.   ...

  7. .net 多线程 Thread ThreadPool Task

    先准备一个耗时方法 /// <summary>/// 耗时方法/// </summary>/// <param name="name">< ...

  8. 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 ...

  9. 转载 .Net多线程编程—任务Task https://www.cnblogs.com/hdwgxz/p/6258014.html

    .Net多线程编程—任务Task   1 System.Threading.Tasks.Task简介 一个Task表示一个异步操作,Task的创建和执行是独立的. 只读属性: 返回值 名称 说明 ob ...

随机推荐

  1. 深入理解 vertical-align 属性

    语法 用来指定行内元素或表格元素的垂直对齐方式 相对父元素的值 baseline 使元素的基线与父元素的基线对齐.HTML规范没有详细说明部分可替换元素的基线,如textarea,这意味着这些元素使用 ...

  2. 阅读GitHub源码的正确打开方式

    前言 近来发现阅读开源项目上手就整最新的代码不合适,缺少项目迭代的具体实现过程,想着若是可以看到针对问题的提交代码就好了,所以就有了本篇博客. 以文主要涉及:如何fork开源项目,如何保证本地仓库代码 ...

  3. 广告行业中那些趣事系列7:实战腾讯开源的文本分类项目NeuralClassifier

    摘要:本篇主要分享腾讯开源的文本分类项目NeuralClassifier.虽然实际项目中使用BERT进行文本分类,但是在不同的场景下我们可能还需要使用其他的文本分类算法,比如TextCNN.RCNN等 ...

  4. 我用STM32MP1做了个疫情监控平台3—疫情监控平台实现

    目录 1.前言 2.数据接口的获取 3.Qt界面的实现 4.在开发板上运行Qt程序 5.使用无线模块联网 6.代码下载 @ 1.前言 之前我使用桌面版本Qt实现了肺炎疫情监控平台:基于Qt的新冠肺炎疫 ...

  5. ALSA driver---DPCM

    https://www.kernel.org/doc/html/v4.11/sound/soc/dpcm.html Description Dynamic PCM allows an ALSA PCM ...

  6. 聊聊OkHttp实现WebSocket细节,包括鉴权和长连接保活及其原理!

    一.序 OkHttp 应该算是 Android 中使用最广泛的网络库了,我们通常会利用它来实现 HTTP 请求,但是实际上它还可以支持 WebSocket,并且使用起来还非常的便捷. 那本文就来聊聊, ...

  7. Dubbo之服务消费原理

    前言 上篇文章<Dubbo之服务暴露>分析 Dubbo 服务是如何暴露的,本文接着分析 Dubbo 服务的消费流程.主要从以下几个方面进行分析:注册中心的暴露:通过注册中心进行服务消费通知 ...

  8. 小白学 Python 数据分析(21):pyecharts 好玩的图表(系列终篇)

    人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析(1):数据分析基础 小白学 Python 数据分析(2):Pandas (一)概述 小白学 Python 数据分析(3):P ...

  9. Nvue/Weex

    Nvue/Weex 使用Uniapp做了一个App,感觉性能不是很好,了解过Uniapp的Nvue,就想做一个纯Nvue项目,其实基本就是做一个Weex项目,不得不说坑是真的多,但是渲染性能真的是没得 ...

  10. 在vscode中配置LeetCode插件,从此愉快地刷题

    大家好,今早在B站看到up主的vscode里藏了leetcode插件,这才知道原来还有这款神器.但是没想到在用的时候遇到了一些麻烦,花了一点时间才解决.所以写这篇文章除了给大家安利这个好用的插件之外, ...