C# --- 多线程和Task-Based Asynchronous Pattern
多线程和异步编程的区别
- 异步编程是一种编程思想, 意思是在遇到一些耗时的任务的时候,不需要等待,而是可以做其他的事情,
- 比如在代码中遇到耗时任务时 (比如和数据库交互, 发送网络请求, 耗时的计算任务), 我们通常希望代码可以多任务同时进行, 保证不阻塞主线程 (比如我们不希望当进行网络请求时, 软件界面卡死)
- 多线程是计算机中的一个概念,线程在进程之中,多个线程可以同时运行,处理不同的事务
- 通常异步编程是通过多线程来实现的.
Thread 和 Task 的区别
Thread
- Thread是low-level concept, 代表计算机中某个进程中的一个线程.
- 多个进程可以同时完成不同的任务
//Example:C#中使用Thread类完成多线程编程
using System;
using System.Threading;
class Program
{
static void Main()
{
// create a new thread
Thread t = new Thread(Worker);
// start the thread
t.Start();
// do some other work in the main thread
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Main thread doing some work");
Thread.Sleep(100);
}
// wait for the worker thread to complete
t.Join();
Console.WriteLine("Done");
}
static void Worker()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Worker thread doing some work");
Thread.Sleep(100);
}
}
}
- 在Java中以及其他一些语言中,多线程编程通常是通过以上的例子来实现的,程序员需要手动创建线程或者线程池来完成(Java多线程)
- 但是多线程编程难度比较高,不适当的线程创建和管理都会极大的影响代码性能
- 所以在C# 提供了一套封装,可以让程序员更方便和安全的使用多线程. 这套封装就是
TAP — Task-based Asynchronous Pattern 也就是经常见到的 task/async/await 关键字
Task
- TAP — Task-based Asynchronous Pattern 是对Thread的封装,方便我们更好的使用多线程,进行异步编程.
- TAP封装了线程池,在TAP中,可以使用关键字会触发使用线程池中的线程 (async/await关键字)
- 对于I/O bound 任务,如网络请求,文件读写等有一些优化
TAP — Task-based Asynchronous Pattern
- C#的异步机制简称为TAP, 也就是核心是基于
Task
和Task<T>
objects, 同时通过async
和await
关键字完成- A Task is a future or a promise. Basically, a Task “promises” to return you a T, but not right now honey, I’m kinda busy, why don’t you come back later?
TAP的两种使用方式之— async/await
- For I/O-bound code, you await an operation that returns a
Task
orTask<T>
inside of anasync
method.
await
- await 表示从这里开始, 代码必须要等待异步方法完成才可以继续进行, 并将控制权交给上一级方法, 并返回一个Promise
Task<TResult>
async
- async 表示这个方法内部调用了 async方法并使用了await
- 如果一个异步方法没有使用await关键字, 则代码则会同步运行.
public async Task<int> GetUrlContentLengthAsync() {
var client = new HttpClient();
// 调用 GetStringAsync 进行网络请求. GetStringAsync 是一个耗时任务, 需要等待请求网站反应
// 此时 GetStringAsync 将控制权交给它的 GetUrlContentLengthAsync, 也就是继续往下运行的意思
// GetStringAsync 返回 Task<TResult> 并赋值给 getStringTask, TResult在这里是一个string类型.
// getStringTask表示这里有个Task,但是还没有完成, 并保证完成后会有一个实际的string值 (一个Promise)
Task<string> getStringTask = client.GetStringAsync("www.google.com");
// DoIndenpendentWork 是一个不需要上面的返回值的一个方法, 继续正常运行
DoIndenpendentWork();
//此时不需要要 getStringTask的任务已经做完了,必须要等待 getStringTask 拿到string value
//如果 getStringTask 还是没有完成, 则 GetUrlContentLengthAsync 会将控制权交给上一级方法.
//并同样返回一个 Task<TResult>. 这个返回值保证了这里最终会有一个string value, 也就是一个Promise
//当 GetStringAsync 完成时, 代码在这里回调 (getStringTask这个Task里会存进拿到的string value)
//继续运行
string contents = await getStringTask;
return contents.length;
}
void DoIndependentWork() {
Console.WriteLine("Working...");
}
异步方法的返回值
- Task, 没有返回值的异步方法必须返回Task
- 有返回值的异步方法返回
Task<TResult>
其中TResult为实际返回类型- Event Handler 返回值可以是void
IAsyncEnumerable<T>
, for an async method that returns an async stream
Example: 返回Task
public static async Task DisplayCurrentInfoAsync()
{
await WaitAndApologizeAsync();
Console.WriteLine($"Today is {DateTime.Now:D}");
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Console.WriteLine("The current temperature is 76 degrees.");
}
static async Task WaitAndApologizeAsync()
{
await Task.Delay(2000);
Console.WriteLine("Sorry for the delay...\n");
}
Example: 返回 Task<Result>
public static async Task ShowTodaysInfoAsync()
{
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await GetLeisureHoursAsync()}";
Console.WriteLine(message);
}
static async Task<int> GetLeisureHoursAsync()
{
DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);
int leisureHours =
today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
? 16 : 5;
return leisureHours;
}
// Example output:
// Today is Wednesday, May 24, 2017
// Today's hours of leisure: 5
TAP的两种使用方式之— Task.Run()
- For CPU-bound code, you await an operation that is started on a background thread with the
Task.Run
method.
//Example 1
public void Demo()
{
var taskParameters = new List<Task>();
foreach (var taskParameter in taskParameters)
{
tasks.Add(RunMyTask(taskParameter));
}
Task.WaitAll(tasks.ToArray());
}
private Task RunMyTask(TaskParameter taskParameter)
{
var task = Task.Run(() => {
//some CPU-Bound code, such as calculation,may use taskParameter here
});
return task;
}
//Example 2
//**CPU-bound example: Perform a calculation for a game**
private DamageResult CalculateDamageDone()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
}
calculateButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
为什么Java没有类似TAP的机制
- Java团队认为TAP机制不能有效的将异步和同步代码相结合 比如all methods in C# doing an await have to be marked as async. 而且异步方法和同步方法在编译阶段是完全不同的, 具体见Oracle工程师的讲座 https://www.youtube.com/watch?v=r6P0_FDr53Q&t=1231s
- 所以Java团队推出了轻量级线程 aka fibers/lightweight threads 降低线程的使用代价