出售域名 11365.com.cn
有需要请联系 16826375@qq.com
在手机上浏览
在手机上浏览

多线程之Task

发布日期:2022-09-12

延申阅读:

1) 多线程之Thread

2) 多线程之线程池

3) 多线程之Task

 

一、什么是Task
Task表示一个基于线程池线程的单元操作,通常以异步方式在线程池线程上执行。

 

二、Task的使用
以实例的方式创建Task

var task1 = new Task(() =>
{
    Console.WriteLine("this is a task(1)");
});
task1.Start(); //创建实例

类方法创建Task

var task2 = Task.Factory.StartNew(() =>
{
    Console.WriteLine("this is a task(2)");
});

var task3 = Task.Run(() => //Task.Run是Task.Factory.StartNew的一种快捷方式
{
    Console.WriteLine("this is a task(3)");
});

Task.WaitAll(task1, task2, task3); //阻塞主线程,直到所有task完成
Console.WriteLine("已完成!");

 

三、Task属性和状态
1、属性
IsCompleted 指明此 Task 是否已完成
IsFaulted 指明Task 是否由于未经处理异常的原因而完成
Status 获取此任务的 TaskStatus

2、状态
1) 初始状态
TaskStatus.Created 该任务已初始化,但尚未被计划。使用Task构造函数创建Task实例时的初始状态。
TaskStatus.WaitingForActivation 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。一个任务的初始状态,这个任务只有当其依赖的任务完成之后才会被调度。
TaskStatus.WaitingToRun 该任务已被计划执行,但尚未开始执行。使用TaskFactory.StartNew创建的任务的初始状态。

2)中间状态
TaskStatus.Running 该任务正在运行,但尚未完成
TaskStatus.WaitingForChildrenToComplete 该任务已完成执行,正在隐式等待附加的子任务完成

3) 最终状态
TaskStatus.Canceled 该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的 CancellationToken 发出了信号。Task属性IsFaulted被设置为true
TaskStatus.Faulted 由于未处理异常的原因而完成的任务。Task属性IsCanceled被设置为true
TaskStatus.RunToCompletion 已成功完成执行的任务。Task属性IsCompleted被设置为true,IsFaulted和IsCanceled被设置为false

 

四、带返回值的Task
线程是一个执行过程,线程本身是没有返回值的概念。Task是运行在线程池线程上的一个任务。我们可以通过Task来得到执行后的结果。

Task.Run(); //无返回值
Task.Run<T>(); //有返回值

//直接执行方法
var task4 = Task.Run<string>(() => { return "返回值"; } );
Console.WriteLine("task4的返回值:"   task4.Result); //返回值

//执行委托方法
var task5 = Task.Run<int>(() => { return Cal(1,2); });
Console.WriteLine("task5的返回值:"   task5.Result); //3

static int Cal(int x, int y) => x   y;

 

五、Task方法
1、Wait
task1.Wait();
阻塞直到Task完成,Task的初衷是并发,所以应尽量减少使用Wait

2、WaitAll
Task.WaitAll(task1, task2, task3); //阻塞主线程,直到所有task完成

3、WaitAny
只要有一个任务执行完成,将不再阻塞

4、CancellationTokenSource、CancellationToken 取消任务Task
当任务执行超长时间,可以取消任务

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;            

var task = Task.Run(()=> {
    Thread.Sleep(2000);
    if(token.IsCancellationRequested) //True
        Console.WriteLine("task任务已取消!");
    else
        Console.WriteLine("task任务正常执行!");
},token);

Console.WriteLine("task状态:"   task.Status); //TaittingToRun

Thread.Sleep(300);
cts.Cancel(); //取消任务

Thread.Sleep(3000);
Console.WriteLine("task状态:"   task.Status); //RunToCompletion

5、ContinueWith 后续操作
task.ContinueWith(t => { Console.WriteLine("task任务已完成,状态:" t.Status); });


六、基于Task的异步编程
1、async,await
await和async必定是成对出现的!
当遇到await时,会把控制权交出给调用方。

static void Main(string[] args)
{
    MyMain();
}

static async void MyMain()
{
    Console.WriteLine("这是MyMain前");
    Task1();
    Console.WriteLine("这是MyMain后");
}

static async Task Task1()
{
    Console.WriteLine("这是task1之前");

    await Task.Run(() =>
    {
        Thread.Sleep(1000);
        Console.WriteLine("这是一秒后的task1");
    });

    Console.WriteLine("这是task1之后");
}

//执行结果:
这是MyMain前
这是task1之前 //往下遇到了await,把控制权交给调用方MyMain,Task1同步执行
这是MyMain后 //控制权在MyMain,继续往下执行
这是一秒后的task1
这是task1之后

2、WhenAny和WhenAll
我们知道WaitAny和WaitAll属性于是同步方法,会阻塞当前线程。而WhenAny和WhenAll却是异步的,而且是并行执行,可以缩短执行时间。

static void Main(string[] args)
{
    Console.WriteLine("主线程Start ID:"   Thread.CurrentThread.ManagedThreadId);
    var watcher2 = new Stopwatch();
    watcher2.Start();
    var reader = ReadUser();
    var lst = reader.Result;
    watcher2.Stop();
    Console.WriteLine("读取用户共用时:"   watcher2.ElapsedMilliseconds / 1000   "秒");
    foreach (var item in lst)
        Console.WriteLine(item);

    Console.WriteLine("主线程End ID:"   Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(1000 * 20);
}

async static Task<List<string>> ReadUser()
{
    Console.WriteLine("进入Task");
    var lst = new List<string>();

    lst.AddRange(await ReadUser1());
    lst.AddRange(await ReadUser2());

    return lst;
}

async static Task<List<string>> ReadUser1()
{
    var lst = new List<string>();
    await Task.Run(() =>
    {
        for (var i = 0; i < 5; i  )
        {
            Thread.Sleep(500); //模拟在读数据库时的时间消耗
            Console.WriteLine($"User-a-{i} ; ID:{Task.CurrentId} ThreadID:{Thread.CurrentThread.ManagedThreadId} Time:{DateTime.Now.ToString()}");
            lst.Add("User-a-"   i);
        }
    });

    return lst;
}

async static Task<List<string>> ReadUser2()
{
    var lst = new List<string>();
    await Task.Run(() =>
    {
        for (var i = 0; i < 10; i  )
        {
            Thread.Sleep(500); //模拟在读数据库时的时间消耗
            Console.WriteLine($"User-b-{i} ; ID:{Task.CurrentId} ThreadID:{Thread.CurrentThread.ManagedThreadId} Time:{DateTime.Now.ToString()}");
            lst.Add("User-b-"   i);
        }
    });

    return lst;
}

可以看出,这里执行了同步操作,ReadUser1花费了2秒多,ReadUser2花费了5秒多,一共是7秒多。
那怎么让他们异步并发执行呢,使只耗时5秒呢?

1)使用WhenAny

async static Task<List<string>> ReadUser()
{
    Console.WriteLine("进入Task");
    var lst = new List<string>();

    var task1 = ReadUser1();
    var task2 = ReadUser2();
    var tasks = new List<Task> { task1, task2 };
            
    while (tasks.Count>0)
    {
        var taskFinished = await Task.WhenAny(tasks);
        if (taskFinished == task1)
        {
            Console.WriteLine("task1 已经完成");
            lst.AddRange(task1.Result);
        }                    
        else if (taskFinished == task2)
        {
            Console.WriteLine("task2 已经完成");
            lst.AddRange(task2.Result);
        }

        tasks.Remove(taskFinished);
    }            

    return lst;
}


2)使用WhenAll

async static Task<List<string>> ReadUser()
{
    Console.WriteLine("进入Task");
    var lst = new List<string>();

    var task1 = ReadUser1();
    var task2 = ReadUser2();
    var tasks = new List<Task> { task1, task2 };
            
    await Task.WhenAll(tasks);
    lst.AddRange(task1.Result);
    lst.AddRange(task2.Result);           

    return lst;
}

实现的效果和上面的一样,但更为简洁。

3)When和Wait的区别
上述代码WhenAll换成WaitAll一样也会并发扫行,区别在于When不会阻塞主线程(前前是异步,有await),而Wait会