< 返回新闻公共列表
云南大王-多线程之旅(Task 任务)
发布时间:2020-04-13 00:00:00
一、Task(任务)和ThreadPool(线程池)不同
源码
1、线程(Thread)是创建并发工具的底层类,但是在前几篇文章中我们介绍了Thread的特点,和实例。可以很明显发现局限性(返回值不好获取(必须在一个作用域中)),当我们线程执行完之后不能很好的进行下一次任务的执行,需要多次销毁和创建,所以不是很容易使用在多并发的情况下。
2、线程池(ThreadPool) QueueUserWorkItem是很容易发起并发任务,也解决了上面我们的需要多次创建、销毁的性能损耗解决了,但是我们就是太简单的,我不知道线程什么时候结束,也没有获取返回值的途径,也是比较尴尬的事情。
3、任务(Task)表示一个通过或不通过线程实现的并发操作,任务是可组合的,使用延续(continuation)可将它们串联在一起,它们可以使用线程池减少启动延迟,可使用回调方法避免多个线程同时等待I/O密集操作。
二、初识Task(任务)
1、Task(任务)是在.NET 4.0引入的、Task是在我们线程池ThreadPool上面进行进一步的优化,所以Task默认还是线程池线程,并且是后台线程,当我们的主线程结束时其他线程也会结束
2、Task创建任务,也和之前差不多
///
/// Task 的使用
/// Task 的创建还是差不多的
///
public static void Show()
{
//实例方式
Task task = new Task(() =>
{
Console.WriteLine("无返回参数的委托");
});
//无参有返回值
Task task1 = new Task(() =>
{
return "我是返回值";
});
//有参有返回值
Task task2 = new Task(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("无返回参数的委托");
});
}
View Code
说明:
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();
View Code
2、WaitAll 等待Task[] 线程数组全部执行成功之后才会执行后续动作
//创建一个装载线程的容器
List list = new List();
for (int i = 0; i < 10; i++)
{
list.Add(Task.Run(() =>
{
Console.WriteLine("WaitAll 执行");
}));
}
Task.WaitAll(list.ToArray());
Console.WriteLine("Wait执行完毕");
View Code
3、WaitAny 等待Task[] 线程数组任一执行成功之后就会执行后续动作
//创建一个装载线程的容器
List list = new List();
for (int i = 0; i < 10; i++)
{
list.Add(Task.Run(() =>
{
Console.WriteLine("WaitAny 执行");
}));
}
Task.WaitAny(list.ToArray());
Console.WriteLine("WaitAny 执行完毕");
View Code
4、WhenAll 等待Task[] 线程数组全部执行成功之后才会执行后续动作、与WaitAll不同的是他有回调函数ContinueWith
//创建一个装载线程的容器
List list = new List();
for (int i = 0; i < 10; i++)
{
list.Add(Task.Run(() =>
{
Console.WriteLine("WhenAll 执行");
}));
}
Task.WhenAll(list.ToArray()).ContinueWith(x =>
{
return x.AsyncState;
});
Console.WriteLine("WhenAll 执行完毕");
View Code
5、WhenAny 等待Task[] 线程数组任一执行成功之后就会执行后续动作、与WaitAny不同的是他有回调函数ContinueWith
//创建一个装载线程的容器
List list = new List();
for (int i = 0; i < 10; i++)
{
list.Add(Task.Run(() =>
{
Console.WriteLine("WhenAny 执行");
}));
}
Task.WhenAny(list.ToArray()).ContinueWith(x =>
{
return x.AsyncState;
});
Console.WriteLine("WhenAny 执行完毕");
Console.ReadLine();
View Code
四、Parallel 并发控制
1、是在Task的基础上做了封装 4.5,使用起来比较简单,如果我们执行100个任务,只能用到10个线程我们就可以使用Parallel并发控制
public static void Show5()
{
//第一种方法是
Parallel.Invoke(() =>
{
Console.WriteLine("我是线程一号");
}, () =>
{
Console.WriteLine("我是线程二号");
}, () =>
{
Console.WriteLine("我是线程三号");
});
//for 方式创建多线程
Parallel.For(0, 5, x =>
{
Console.WriteLine("这个看名字就知道是for了哈哈 i=" + x);
});
//ForEach 方式创建多线程
Parallel.ForEach(new string[] { "0", "1", "2", "3", "4" }, x => Console.WriteLine("这个看名字就知道是ForEach了哈哈 i=" + x));
//这个我们包一层,就不会卡主界面了
Task.Run(() =>
{
//创建线程选项
ParallelOptions parallelOptions = new ParallelOptions()
{
MaxDegreeOfParallelism = 3
};
//创建一个并发线程
Parallel.For(0, 5, parallelOptions, x =>
{
Console.WriteLine("限制执行的次数");
});
}).Wait();
Console.WriteLine("**************************************");
//Break Stop 都不推荐用
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 3;
Parallel.For(0, 40, parallelOptions, (i, state) =>
{
if (i == 20)
{
Console.WriteLine("线程Break,Parallel结束");
state.Break();//结束Parallel
//return;//必须带上
}
if (i == 2)
{
Console.WriteLine("线程Stop,当前任务结束");
state.Stop();//当前这次结束
//return;//必须带上
}
Console.WriteLine("我是线程i=" + i);
});
}
View Code
五、多线程实例
1、代码异常我信息大家都不陌生,比如我刚刚写代码经常会报 =>对象未定义null 的真的是让我心痛了一地,那我们的多线程中怎么去处理代码异常呢? 和我们经常写的同步方法不一样,同步方法遇到错误会直接抛出,当是如果我们的多线程中出现代码异常,那么这个异常会自动传递调用Wait 或者 Task 的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();
}
}
View Code
///
/// 多线程捕获异常
/// 多线程会将我们的异常吞了,因为我们的线程执行会直接执行完代码,不会去等待你捕获到我的异常。
/// 我们的线程中最好是不要出现异常,自己处理好。
///
public static void Show()
{
//创建一个多线程工厂
TaskFactory taskFactory = new TaskFactory();
//创建一个多线程容器
List tasks = new List();
//创建委托
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 = 0; i < 10; i++)
{
tasks.Add(taskFactory.StartNew(action));
}
Task.WaitAll(tasks.ToArray());
}
catch (Exception ex)
{
Console.WriteLine("异常啦");
}
Console.WriteLine("我已经执行完了");
}
View Code
2、多线程取消机制,我们的Task在外部无法进行暂停 Thread().Abort() 无法很好控制,上上篇中Thread我们也讲到了Thread().Abort() 的不足之处。有问题就有解决方案。如果我们使用一个全局的变量控制,就需要不断的监控我们的变量取消线程。那么说当然有对应的方法啦。CancellationTokenSource (取消标记源)我们可以创建一个取消标记源,我们在创建线程的时候传入我们取消标记源Token。Cancel()方法 取消线程,IsCancellationRequested 返回一个bool值,判断是不是取消了线程了。
///
/// 多线程取消机制 我们的Task在外部无法进行暂停 Thread().Abort() 无法很好控制,我们的线程。
/// 如果我们使用一个全局的变量控制,就需要不断的监控我们的变量取消线程。
/// 我们可以创建一个取消标记源,我们在创建线程的时候传入我们取消标记源Token
/// Cancel() 取消线程,IsCancellationRequested 返回一个bool值,判断是不是取消了线程了
///
public static void Show1()
{
//创建一个取消标记源
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
//创建一个多线程工厂
TaskFactory taskFactory = new TaskFactory();
//创建一个多线程容器
List tasks = new List();
//创建委托
Action