CS5L7-2——Task类
CS5L7-2——Task类
本章代码关键字
1 | System.Threading.Tasks //Task类的命名空间 |
Task
-
Task
类是基于Thread的封装 -
Task
类可以有返回值,Thread没有返回值 -
Task
类可以执行后续操作,Thread没有这个功能 -
Task
可以更加方便的取消任务,Thread相对更加单一 - Task具备ThreadPool线程池的优点,更节约性能
命名空间:System.Threading.Tasks
类名:Task
Task
顾名思义就是任务的意思,Task
是在线程池基础上进行的改进,它拥有线程池的优点,同时解决了使用线程池不易控制的弊端
它是基于线程池的优点对线程的封装,可以让我们更方便高效的进行多线程开发
简单理解:
Task
的本质是对线程 Thread
的封装,它的创建遵循线程池的优点,并且可以更方便的让我们控制线程
一个 Task
对象就是一个线程
创建无返回值Task的三种方式
-
通过
new
一个Task
对象传入委托函数并启动传入的这个委托函数就是多线程要执行的方法
值得一提的是,虽然Task
的启动和Thread
很相似,这里的Task
运 行是基于线程池的运行,因此它的性能会比直接开启线程更好
以及,由 Task
打开的多线程仍然需要自行关闭,否则会与编辑器共生1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22private bool isRunning = true;
void Start()
{
Task t1 = new Task(() =>
{
int i = 0;
while (isRunning)
{
print("方法一:" + i);
i++;
Thread.Sleep(1000);
}
});
t1.Start();
}
private void OnDestroy()
{
isRunning = false;
} -
通过
Task
中的Run
静态方法传入委托函数1
2
3
4
5
6
7
8
9
10Task t2 = Task.Run(() =>
{
int i = 0;
while (isRunning)
{
print("方法二:" + i);
i++;
Thread.Sleep(1000);
}
}); -
通过
Task.Factory
中的StartNew
静态方法传入委托函数1
2
3
4
5
6
7
8
9
10Task t3 = Task.Factory.StartNew(() =>
{
int i = 0;
while (isRunning)
{
print("方法三:" + i);
i++;
Thread.Sleep(1000);
}
});
创建有返回值的Task
创建有返回值的Task,只需要使用泛型方法即可
-
通过
new
一个Task<>
对象传入委托函数并启动传入的这个委托函数就是多线程要执行的方法
泛型填入返回值的类型,在委托里加上返回值1
2
3
4
5
6
7
8
9
10
11
12Task<int> t1 = new Task<int>(() =>
{
int i = 0;
while (isRunning)
{
print("方法一:" + i);
i++;
Thread.Sleep(1000);
}
return 1;
});
t1.Start(); -
通过
Task
中的Run<>
静态泛型方法传入委托函数1
2
3
4
5
6
7
8
9
10
11Task<string> t2 = Task.Run<string>(() =>
{
int i = 0;
while (isRunning)
{
print("方法二:" + i);
i++;
Thread.Sleep(1000);
}
return "任务完成";
}); -
通过
Task.Factory
中的StartNew<>
静态泛型方法传入委托函数1
2
3
4
5
6
7
8
9
10
11Task<float> t3 = Task.Factory.StartNew<float>(() =>
{
int i = 0;
while (isRunning)
{
print("方法三:" + i);
i++;
Thread.Sleep(1000);
}
return 4.5f;
});
获取传入Task方法的返回值
注意
task.Result
获取结果时会阻塞线程!即如果task
没有执行完成,会等待task
执行完成获取到Result
,然后再执行后边的代码,
也就是说执行到这句代码时,由于我们的Task
中是死循环,所以执行task.Result
的线程就会被卡死这意味着,如果我们编写逻辑不当,造成多线程内执行的方法始终没有
return
,可能导致整个主程序卡死,例如,Unity无法继续运行等问题
1 | private void Start() |
同步执行Task
刚才我们举的例子都是通过多线程异步执行的
(举个例子,下面这段代码里,print("主线程执行");
会先于线程开启)
1 | private void Start() |
如果你希望 Task
能够同步执行,只需要调用 Task
对象中的 RunSynchronously
方法
注意:需要使用
new Task
对象的方式,因为Run
和StartNew
在创建时就会启动
1 | private void Start() |
Task中线程阻塞的方式(任务阻塞)
-
Wait()
方法:使执行该方法的线程等待任务执行完毕,再执行后面的内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Task t1 = Task.Run(() =>
{
for (int i = 0; i < 5; i++)
{
print("t1: " + i);
}
});
Task t2 = Task.Run(() =>
{
for (int i = 0; i < 20; i++)
{
print("t2: " + i);
}
});
t1.Wait();
print("主线程执行"); -
WaitAny()
静态方法:传入任务中任意一个任务结束就继续执行1
2Task.WaitAny(t1, t2);
print("主线程执行"); -
WaitAll()
静态方法:任务列表中所有任务执行结束就继续执行1
2Task.WaitAll(t1, t2);
print("主线程执行");
Task完成后继续其它Task(任务延续)
先声明并执行两个线程
1 | Task t1 = Task.Run(() => |
-
WhenAll()
静态方法 +ContinueWith()
方法:传入的任务都完毕后再执行某任务,需要一个以task
参数的方法,传入新开始的task1
2
3
4
5
6
7
8
9
10
11
12Task.WhenAll(t1, t2).ContinueWith((t) =>
{
print("一个新的任务开始了");
int i = 0;
while (isRunning)
{
print(i);
i++;
Thread.Sleep(1000);
}
print("任务结束");
}); -
Task.Factory.ContinueWhenAll()
静态方法:相当于上面两个方法的融合- 参数一:Task数组,要等待完成的所有Task任务
- 参数二:参数一的所有任务完成后要执行的方法
1
2
3
4
5
6
7
8
9
10
11
12Task.Factory.ContinueWhenAll(new Task[] { t1, t2 }, (t) =>
{
print("一个新的任务开始了");
int i = 0;
while (isRunning)
{
print(i);
i++;
Thread.Sleep(1000);
}
print("任务结束");
}); -
WhenAny()
静态方法 +ContinueWith()
方法:传入任务只要有一个执行完毕后再执行某任务1
2
3
4
5
6
7
8
9
10
11
12Task.WhenAny(t1, t2).ContinueWith((t) =>
{
print("一个新的任务开始了");
int i = 0;
while (isRunning)
{
print(i);
i++;
Thread.Sleep(1000);
}
print("任务结束");
}); -
Task.Factory.ContinueWhenAny()
静态方法:相当于上面两个方法的融合
参数一:Task
数组,要等待完成的所有Task
任务
参数二:参数一的只要有一个任务完成后要执行的方法1
2
3
4
5
6
7
8
9
10
11
12Task.Factory.ContinueWhenAny(new Task[] { t1, t2 }, (t) =>
{
print("一个新的任务开始了");
int i = 0;
while (isRunning)
{
print(i);
i++;
Thread.Sleep(1000);
}
print("任务结束");
});
取消Task执行
-
通过加入
bool
标识 控制线程内死循环的结束上面已经有诸多的例子使用了这种方法,这里是其中一个例子:Task()
-
取消标识源类
通过
CancellationTokenSource
取消标识源类,来控制CancellationTokenSource
对象可以达到延迟取消、取消回调等功能1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30CancellationTokenSource c;
private void Start()
{
c = new CancellationTokenSource();
c.CancelAfter(5000); //延迟5000毫秒也就是5秒后使IsCancellationRequested属性为true
c.Token.Register(() => //当IsCancellationRequested属性为true了要执行的回调函数
{
print("任务取消了");
});
Task.Run(() =>
{
int i = 0;
while (!c.IsCancellationRequested) //这是该取消标识源对象的是否取消请求的bool值,可以用这个来控制线程循环的结束,默认为false
{
print("计时:" + i);
i++;
Thread.Sleep(1000);
}
});
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
c.Cancel(); //这会使IsCancellationRequested属性为true
}
}-
是否取消请求属性
这是该取消标识源对象的是否取消请求的
bool
值,可以用这个来控制线程循环的结束,默认为false
1
c.IsCancellationRequested
-
延迟取消方法
传入延迟多少毫秒取消
1
c.CancelAfter(5000);
-
取消回调
取消后的回调方法
1
2
3
4c.Token.Register(() =>
{
print("任务取消了");
});
-