Task
Task简化了线程操作。
Task 使用又离不开async
和await
,如何正确的使用Task
,以一个简单的示例演示。
void Print()
{
Console.WriteLine($"Print---{DateTime.Now.ToString("HH:mm:ss")}");
}
async void PrintAsync(int times)
{
while (times > 0)
{
Console.WriteLine($"PrintAsync---{DateTime.Now.ToString("HH:mm:ss")}");
times--;
await Task.Delay(1000);
}
}
async Task PrintTaskAsync(int times)
{
while (times > 0)
{
Console.WriteLine($"PrintTaskAsync---{DateTime.Now.ToString("HH:mm:ss")}");
times--;
await Task.Delay(1000);
}
}
同步
首先看单独的方法调用
Print();
PrintAsync();
await PrintTaskAsync();
当返回值类型为Task
时才需要使用await
。
方法前加不加await
的区别是什么?看输出结果。
Print();
// 输出
// Print---12:11:12
PrintAsync(3);
// 输出
// PrintAsync---12:11:12
await PrintTaskAsync(3);
// 输出
// PrintTaskAsync---12:11:12
// PrintTaskAsync---12:11:13
// PrintTaskAsync---12:11:14
方法PrintAsync
应该输出三次,但实际只输出了一次,为何?
断点调试,发现执行await Task.Delay(1000);
时程序结束了,这里又产生了一个问题,为何结束了,不继续执行?
在看一个示例,将Print
方法等待三秒
void Print()
{
Console.WriteLine($"Print---{DateTime.Now.ToString("HH:mm:ss")}");
Thread.Sleep(3000);
Console.WriteLine("Print---End");
}
然后调用方法
Print();
PrintAsync(3);
// 输出
// Print---12:11:12
// Print---End
// PrintAsync---12:11:12
跟之前没什么区别,PrintAsync
,然后交换下方法顺序
PrintAsync(3);
Print();
//输出
// PrintTaskAsync---12:11:12
// Print---12:11:12
// PrintTaskAsync---12:11:13
// PrintTaskAsync---12:11:14
// Print---End
由此可以看出,问题在于await Task.Delay(1000);
,它是一个异步线程,当出现耗时操作时,不会阻塞当前代码,而是继续执行之后的代码。
当调用如下代码时
Print();
PrintAsync(3);
PrintAsync
虽然没有执行完,但PrintAsync
之后已没有可执行的代码。
而切换顺序执行
PrintAsync(3);
Print();
由于PrintAsync
执行输出第一条信息时,等待await Task.Delay(1000);
执行前,代码继续执行之后的Print
方法,由于Print
需要耗时3秒,在等待Print
输出结果时,PrintAsync
又继续输出了第二条信息,甚至第三条。
当Print
耗时时间小于PrintAsync
时,PrintAsync
输出记录数是不全的。
更近一步,将PrintAsync
换成PrintTaskAsync
即带有Task返回值的方法
不管是
Print();
await PrintTaskAsync(3);
还是
await PrintTaskAsync(3);
Print();
都能正常的输出预期结果。
因此可以确认以下结果:
- void 异步依赖执行上下文,当异步和同步同时出现时,可能发生不可预料的结果
- void 异步改为Task异步符合正常预期
- 当所有执行代码都为异步时,执行符合预期
- 当同步方法里里有
await
或者async
修饰时,正如其名,它是一个异步方法。
如果不想使用await Task.Delay(1000);
输出三次那么使用Thread.Sleep(1000);
达到目标。
如果非要使用await Task.Delay(1000)
实现间隔等待,将void
返回类型改为Task
即可,这也是为什么推荐无参数返回时推荐使用Task
的原因。
另外也可以将Print
方法改为异步。
异步
在上面同步使用时,分析得出了await
、async
就是异步执行。
那么如何 将一个同步方法改为异步。
常见的有
await Task.Run(() => Print());
await Task.Factory.StartNew(()=>Print());
Task
var t = new Task(Print); t.Start();
await
async void Print() { Console.WriteLine($"Print---{DateTime.Now.ToString("HH:mm:ss")}"); await Task.Sleep(1); }
并发
常说Task会提升效率,但是就算增加了异步,虽然不阻塞当前代码,但是执行的总时间还是保持不变。
那么如果使用Task缩小执行时间。
还是以上面同步的代码示例为例。增加一个方法执行时间的计算。
void Print(int times)
{
var sw = new Stopwatch();
sw.Start();
while (times > 0)
{
Console.WriteLine($"Print---{DateTime.Now.ToString("HH:mm:ss")}");
times--;
Thread.Sleep(1000 );
}
sw.Stop();
Console.WriteLine($"Print---执行完毕耗时:{sw.ElapsedMilliseconds}");
}
async Task PrintTaskAsync(int times)
{
var sw = new Stopwatch();
sw.Start();
while (times > 0)
{
Console.WriteLine($"PrintTaskAsync---{DateTime.Now.ToString("HH:mm:ss")}");
times--;
await Task.Delay(1000);
}
sw.Stop();
Console.WriteLine($"PrintAsync---执行完毕耗时:{sw.ElapsedMilliseconds}");
}
调用方法
var sw=new Stopwatch();
sw.Start();
var t1=Task.Run(()=>Print(3));
var t2= PrintTaskAsync(3);
Task.WaitAll(new Task[] { t1, t2 });
sw.Stop();
Console.WriteLine($"总耗时:{sw.ElapsedMilliseconds}");
![Task并行](https://nas.ilyl.life:8092/dotnet/task.gif)
Task还提供其他方法,参考官方文档即可。