async await task

首先搞清楚什么是同步什么是异步
Synchronize 同步
asynchronous 异步
相差也就是一个a,也可以理解为a开头的就是异步操作,
同步一般是:当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行
异步一般是:方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕

哲学上思考,同步更规则性,代码会很好的顺序执行一般不会出错。异步必然伴随着各种各样的情况,但是很明显在一些耗时操作的时候能帮助减少时间

以C#为例子进行具体的学习:C#中三个关键词并不是同时发布的,task最先在dotnet中使用,可见先搞懂task是很重要的

TASK

task建立在线程池的基础上,线程池也就是管理一堆线程的一东西,为什么需要进行管理呢,需要的时候创建不就行了吗?这里涉及到操作系统,创建一个线程带来的开销远大于保持一个线程需要的时候再用的开销大,而线程池并不是很高大上,功能仅仅是保存和分配线程这点功能,只用线程池不能控制线程池中线程的执行顺序,也不能获取线程池内线程取消/异常/完成的通知。
task可以说是一个线程池pro max,他添加了这些功能,但也是一次只能运行一个函数。

使用的的办法有3种
一:new一个新的task,当想要使用的时候myNewTask.Start()
实例如下
Task task = new Task(() =>
{
Thread.Sleep(100);
Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");
});
task.Start();
二:创建并开启一个task,Task.Factory.NewStart()
实例如下
Task task2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
});
三:Task.Run() 将任务放在线程池队列,返回并启动一个Task
Task task3 = Task.Run(() =>
{
Thread.Sleep(100);
Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine("执行主线程!");
Console.ReadKey();
这是没有返回值的例子,有返回值的也很简单
比如
Task task = new Task(() =>
{
return $"hello, task1的ID为{Thread.CurrentThread.ManagedThreadId}";
});
task.Start();
Console.WriteLine(task.Result);

思考一下,可以知道,有返回值的情况下,获取result必然是会阻塞主线程的,不阻塞会引发会乱
另外提一嘴 ,有种奇怪的运行方式task.RunSynchronously();,这个会同步运行一个task。
在使用Tread的时候,一般使用Join来阻塞主线程,类似的,Task也有这类功能,并且还更加完善static void Main(string[] args)
{
Task task1 = new Task(() => {
Thread.Sleep(500);
Console.WriteLine("线程1执行完毕!");
});
task1.Start();
Task task2 = new Task(() => {
Thread.Sleep(1000);
Console.WriteLine("线程2执行完毕!");
});
task2.Start();
//阻塞主线程。task1,task2都执行完毕再执行主线程
       //执行【task1.Wait();task2.Wait();】可以实现相同功能
Task.WaitAll(new Task[]{ task1,task2});
Console.WriteLine("主线程执行完毕!");
Console.ReadKey();
}
Task也有类似的操作用来阻塞主线程:
一 waitAll
Task.WaitAll(new Task[]{ task1,task2});
这个函数接受一个Task数组,会一直阻塞直到里面的都执行完毕
二 wait
上面的例子中也可以写作
task1.Wait();
task2.Wait();
三 WaitAny
函数用法和一中的类似,不过是只要任意一个完成就会停止阻塞,用处也挺大但没这么大

管道
很多语言中都有相通的思想,比如golang 的管道channel,在C#异步编程中也有体现
CancellationTokenSource这个类用来控制一个Task什么时候停止
举例来说
CancellationTokenSource source = new CancellationTokenSource();
int index = 0;
//开启一个task执行任务
Task task1 = new Task(() =>
{
while (!source.IsCancellationRequested)
{
Thread.Sleep(1000);
Console.WriteLine($"第{++index}次执行,线程运行中...");
}
});
task1.Start();
//五秒后取消任务执行
Thread.Sleep(5000);
//source.Cancel()方法请求取消任务,IsCancellationRequested会变成true
source.Cancel();
在使用winform的时候,经常会遇到修改ui的操作,这个时候必须使用异步才能不阻塞ui线程,不然会让应用好像“卡了”一样,

二 await
首先需要说明清楚,Task是一个类,其子类有一个Result。并且Task不能像其他类一样简单的理解为一个有很多方法的结构体,我更倾向于将这个理解为一个“操作”,其内部有很多普通类没有的线程操作。一定要在前面加上await来调用,这个和函数很不一样
我认为这是一种比较好的调用范式
static async Task Main(string[] args)
{
// 开始异步读取文件
Task fileReadTask = GetContentAsync(Environment.CurrentDirectory + @"/test.txt");

    // 在读取文件期间处理用户输入
    Console.WriteLine("请输入您的名字:");
    string userName = Console.ReadLine();
    Console.WriteLine($"您好,{userName}!文件正在读取中,请稍等。");

    // 在读取文件期间执行计算任务
    Task<int> calculationTask = PerformSomeCalculationAsync();
    
    // 等待文件读取完成
    string content = await fileReadTask;
    Console.WriteLine("文件内容读取完成:");
    Console.WriteLine(content);

    // 等待计算任务完成
    int calculationResult = await calculationTask;
    Console.WriteLine($"计算任务完成,结果是:{calculationResult}");
    
    Console.ReadKey();
}

// 异步读取文件内容
async static Task<string> GetContentAsync(string filename)
{
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        var bytes = new byte[fs.Length];
        Console.WriteLine("开始读取文件");
        int len = await fs.ReadAsync(bytes, 0, bytes.Length);
        string result = Encoding.UTF8.GetString(bytes);
        return result;
    }
}

// 模拟一个异步计算任务
async static Task<int> PerformSomeCalculationAsync()
{
    Console.WriteLine("开始计算任务...");
    await Task.Delay(3000); // 模拟一个耗时的计算
    Console.WriteLine("计算任务进行中...");
    return 42; // 假设计算结果是42
}
在这个例子中能像同步编程一样使用异步编程,只需要多声明一下Task和await,async
这背后有一个复杂的任务调度和上下文恢复机制,dotnet是很复杂的,我作为C# 的初学者并没有能够具体搞懂其中的奥妙,但是我可以简单的理解为,定义了Task之后,可以由await进行调用,并且与之相关的一系列代码都会进入一个新的线程中完成,主线程会绕过与之相关的代码,去完成接下来的任务,至于async,很遗憾我也不能解释清楚,但是显然的是,使用了这一套方法的函数和普通函数相当不一样,内部有一套自己的处理逻辑,async更像是告诉编译器的一种声明("你对我要特殊对待")