C# 多线程三:Task

Task是.NET 3.0中推出的,是基于ThreadPool封装的,里面的线程都是来自于ThreadPool。

1、使用Run()方法启动线程

F12查看Run()方法的定义:

发现Run()方法的参数是一个Action类型的委托,那么可以使用下面的方式启动多线程:

 // 使用Run()方法启动线程
 Task.Run(() => this.DoSomethingLong("btnTask_Click1"));
 Task.Run(() => this.DoSomethingLong("btnTask_Click2"));

2、使用TaskFactory的StartNew()方法

 // 使用TaskFactory启动
 TaskFactory taskFactory = Task.Factory;
 taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click3"));

3、使用Task的构造函数启动

 // 使用构造函数启动
 Task task= new Task(() => this.DoSomethingLong("btnTask_Click4"));
 task.Start();

下面来看看一个案例:

拿编写程序为例来说明:在一个项目中,项目经理负责前期的一些准备工作,相当于是主线程,其他开发人员负责具体的编码工作,相当于子线程,那么可以使用这个例子来模仿多线程。

1、先编写一个编码的方法:

/// <summary>
/// 编码的方法
/// </summary>
/// <param name="name">开发人员Name</param>
/// <param name="project">负责的模块</param>
private void Coding(string name, string project)
{
       Console.WriteLine($"****************Coding {name} Start {project} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
       long lResult = 0;
       for (int i = 0; i < 1000000000; i++)
       {
            lResult += i;
       }
       //Thread.Sleep(2000);

       Console.WriteLine($"****************Coding {name}   End {project} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
}

2、启动线程

Console.WriteLine($"项目经理启动一个项目。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"前置的准备工作。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"开始编程。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Task.Run(() => this.Coding("Tom", "Client"));
Task.Run(() => this.Coding("Jack", "Service"));

结果:

这时又提出了新的需求:必须所有编码工作都完成以后才能上线使用,将代码修改如下:

Console.WriteLine($"项目经理启动一个项目。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"前置的准备工作。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"开始编程。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Task.Run(() => this.Coding("Tom", "Client"));
Task.Run(() => this.Coding("Jack", "Service"));
 Console.WriteLine($"告诉甲方验收,上线使用【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");

再次运行程序,效果如下:

从上面的截图中看出:这不是我们想要的效果,编码工作还没有结束就可以上线使用了,即子线程还没有结束,主线程就已经结束了。要想实现我们想要的效果,那么必须使主线程等待子线程都结束以后,主线程才能结束。这时可以使用Task提供的WaitAll()方法实现,F12查询WaitAll()方法的定义:

WaitAll()方法有很多重载,我们在这里使用第一个重载方法,即参数是Task[]数组。查看Run()方法的定义时,我们会发现Run()方法的返回值就是Task类型,那么我们可以将代码进行如下的修改:

List<Task> taskList = new List<Task>();
Console.WriteLine($"项目经理启动一个项目。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"前置的准备工作。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"开始编程。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
taskList.Add( Task.Run(() => this.Coding("Tom", "Client")));
taskList.Add( Task.Run(() => this.Coding("Jack", "Service")));
// 等待集合中的所有线程都执行完
Task.WaitAll(taskList.ToArray());
Console.WriteLine($"告诉甲方验收,上线使用【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");

然后运行程序,查看效果:

可以看出:这次就是我们想要的效果。但是在运行程序的时候会发现,使用了WaitAll()方法以后,界面会卡住,也就是说WaitAll()方法会阻塞当前线程,等着全部任务都执行完以后,才会进入下一行。

注意:WaitAll()除了上面使用的方法以外,还有带时间参数的重载方法,表示会等待多长时间,无论所有任务是否都完成。例如:

List<Task> taskList = new List<Task>();
Console.WriteLine($"项目经理启动一个项目。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"前置的准备工作。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"开始编程。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
taskList.Add( Task.Run(() => this.Coding("Tom", "Client")));
taskList.Add( Task.Run(() => this.Coding("Jack", "Service")));
//限时等待,最多等待1秒
Task.WaitAll(taskList.ToArray(), 1000);
Console.WriteLine("等待1s之后,执行的动作");
// 等待集合中的所有线程都执行完
Task.WaitAll(taskList.ToArray());               
Console.WriteLine($"告诉甲方验收,上线使用【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");

效果:

这时需求又有了变化:只要其中一个模块完成,就相当于完成了里程碑的工作,这时可以用Task提供的WaitAny()方法实现。F12查询WaitAny()的定义:

WaitAny()表示等待其中任何一个任务完成就会进入下一行,代码修改如下:

List<Task> taskList = new List<Task>();
Console.WriteLine($"项目经理启动一个项目。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"前置的准备工作。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"开始编程。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
taskList.Add( Task.Run(() => this.Coding("Tom", "Client")));
taskList.Add( Task.Run(() => this.Coding("Jack", "Service")));
Task.WaitAny(taskList.ToArray());              
Console.WriteLine($"完成里程碑 【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
限时等待,最多等待1秒
//Task.WaitAll(taskList.ToArray(), 1000);
//Console.WriteLine("等待1s之后,执行的动作");
 等待集合中的所有线程都执行完
Task.WaitAll(taskList.ToArray());               
Console.WriteLine($"告诉甲方验收,上线使用【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");

 效果:

注意:WaitAny()同样也会阻塞当前线程,卡住界面。和WaitAll()一样,WaitAny()也有带时间参数的重载方法,表示等待多长时间。

应用场景:

1、WaitAll():假如一个界面需要的数据,来自不同的数据源,那么这时可以使用WaitAll()等待所有的数据都查询完成以后才显示界面。

2、WaitAny():商品查询,只要查询出某一个符合条件的即可。

上面讲到的WaitAll()和WaitAny()都会卡住界面,那么有没有不卡界面的方法呢?答案是肯定的:那就是WhenAll()和WhenAny().。

从上面的截图中可以看出,WhenAll()的参数还是一个Task[]类型的数组,返回值是Task类型,表示Task[]数组里面的所有任务都完成以后,创建一个新的Task。这时可以继续调用Task类提供的ContinueWith()方法。ContinueWith()方法表示Task任务完成时异步执行的延续任务。

List<Task> taskList = new List<Task>();
Console.WriteLine($"项目经理启动一个项目。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"前置的准备工作。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
Console.WriteLine($"开始编程。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
taskList.Add(Task.Run(() => this.Coding("Tom", "Client")));
taskList.Add(Task.Run(() => this.Coding("Jack", "Service")));

Task.WhenAny(taskList.ToArray()).ContinueWith(t =>
{
         Console.WriteLine($"我先完成了。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
});

Task.WhenAll(taskList.ToArray()).ContinueWith(t =>
{
         Console.WriteLine($"部署环境,联调测试。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
 });

 效果:

注意:

除了Task可以实现这种效果以为,TaskFactory也可以实现,例如:

TaskFactory taskFactory = new TaskFactory();
taskFactory.ContinueWhenAll(taskList.ToArray(), tList =>
{
          Console.WriteLine($"部署环境,联调测试。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
});

taskFactory.ContinueWhenAny(taskList.ToArray(), t =>
{
           Console.WriteLine($"部署环境,联调测试。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
});

 

 

 

 

 

 

 

 

 

 

在Java中,List<String>是一个接口类型,而ArrayList<String>是List<String>接口的一个实现类。声明为List<String>而不是ArrayList<String>的原因是为了更好地遵循面向接口编程的原则,以便在需要时可以轻松地切换到其他List接口的实现类,而不需要修改太多的代码。这样可以提高代码的灵活性和可维护性。\[1\] 在C#中,List<string>是一个泛型类,用于存储一系列字符串。它提供了许多方便的方法来操作和管理这些字符串。使用List<string>可以更方便地添加、修改和删除字符串元素。\[2\]\[3\] #### 引用[.reference_title] - *1* [List<String> list=new ArrayList<String>(20);为什么要声明为List 而不是ArrayList<String>?](https://blog.csdn.net/dibengshao7358/article/details/102209914)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [C#中的List<string>泛型类示例](https://blog.csdn.net/weixin_30652491/article/details/98790014)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Java中,常用的List的五种声明并赋值操作](https://blog.csdn.net/Letol/article/details/127485553)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值