特点/原理

特点

  • 异步编程并不能提高运行效率,只能提高同时处理任务的速率(并发)
  • 异步方法返回值一般是Task,其中T就是返回值的类型
  • 异步方法一般命名需要在名称后加上Async
  • 如果异步方法没有返回值,void也要写成Task
  • 在调用异步方法时,需要加上await关键字修饰,如果没加上await关键字调用异步方法,程序则不会等待,直接执行下一行代码,并且得到的返回值会是Task
  • 异步方法具有传染性,如果使用了await关键字,方法也必须使用async关键字修饰
  • 异步委托也需要加上async关键字修饰
  • 不能使用out、ref参数 ```csharp string filename = @”D://1.txt”; await File.WriteAllTextAsync(filename, “hello async await”); string s = await File.ReadAllTextAsync(filename);

ThreadPool.QueueUserWorkItem( async (obj) => { await File.ReadAllTextAsync(“D://1.txt”); });

  1. <a name="lHNRO"></a>
  2. ## 执行原理
  3. - 在执行异步方法时,异步方法的代码会被拆分了对MoveNext方法的多次调用
  4. - 在await等待的过程中,如果执行时间较长,异步调用前的线程被返回线程池中,等待结束后会返回新的线程,后续的代码会在这个新的线程中继续运行
  5. - 在不影响系统并发的情况下,尽量减少线程切换
  6. ```csharp
  7. Console.WriteLine(Thread.CurrentThread.ManagedThreadId);//打印线程ID
  8. StringBuilder sb = new StringBuilder();
  9. //构建一个大字符串进行耗时操作
  10. for (int i = 0; i < 10000; i++)
  11. {
  12. sb.Append("dfasdgdgsaafdsgfdahgdf");
  13. }
  14. await File.WriteAllTextAsync("D://1.txt",sb.ToString());//调用异步方法
  15. Console.WriteLine(Thread.CurrentThread.ManagedThreadId);//打印线程ID

异步方法注意事项

异步不等于多线程

如果方法只是修饰了async关键字,并不代表会切换到新的线程,方法内使用了await关键字,才会按照异步执行,否则只是还是会按照同步方法执行,如果需要手动新开线程,可以使用Task.Run();方法或Task.Factory.StartNew方法进行。可以理解成,方法只是修饰了async,返回值是Task/Task的方法,会按照同步执行,并且不会创建新线程。

没有async的异步方法

await的作用相当于把返回类型Task的值的类型转换(取出)成了T的类型,如下方代码,ReadFileAsync并没有修饰async,调用的异步方法也没有修饰await,所以返回值是Task,而在调用时修饰了await,就把值给取了出来并转换为了string类型

string outsrt = await ReadFileAsync(1);
Console.WriteLine(outsrt);

Task<string> ReadFileAsync(int num)
{
    switch (num)
    {
        case 1:
            return File.ReadAllTextAsync("D://1.txt");
        case 2:
            return File.ReadAllTextAsync("D://3.txt");
        default:
            throw new ArgumentException("num invalid");
    }
}

上方是异步的方式调用没有async修饰的异步方法,下方是同步方法中调用异步方法,但尽量不这么做,会存在风险:死锁

string s1 = File.ReadAllTextAsync().Result;//有返回值
string s2 = File.ReadAllTextAsync().GetAwaiter().GetResult();//有返回值
File.WriteAllTextAsync().Wait();//无返回值

综上所述,Task才是异步方法的精髓,async/await是微软提供的语法糖,使得用户更好的使用异步方法。

异步编程中的暂停

不可以使用Thread.Sleep()方法,否则会阻塞线程,而要使用Task.Delay()。

CancellationToken

作用:异步方法传入CancellationToken值,用于中断执行操作,比如调用了一个长时间运算的方法,但是中途中断了(比如浏览器关了),如果没有CancellationToken,服务器不会停止运算,造成资源浪费,而传入CancellationToken后,会根据设定的规则停止当前线程运行。
ASP.NET CORE的异步方法中,有CancellationToken的方法尽量都传

public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
    DownloadAsync("https://www.youzack.com",1000,cancellationToken);
}

static async Task DownloadAsync(string url,int n,CancellationToken cancellationToken)
{
    using (HttpClient client = new HttpClient())
    {
        for(int i =0;i < n; i++)
        {
            var resp = await client.GetAsync(url,cancellationToken);
            string html = await resp..Content.ReadAsStringAsync();
            Debug.WriteLine(html);//ASP.NET中使用Debug打印内容,输出到输出窗口
        }
    }
}

等待多个Task运行

WhenAny

或,有一个完成Task就完成了

WhenAll

与,全都完成了Task才完成,用于等待多个任务执行结束,但是不在乎它们的执行顺序。

FormResult

创建普通数值的Task对象

Task<string> t1 = File.ReadAllTextAsync("D://1.txt");
Task<string> t2 = File.ReadAllTextAsync("D://2.txt");
Task<string> t3 = File.ReadAllTextAsync("D://3.txt");

//Task.WhenAll
string[] results = await Task.WhenAll(t1,t2,t3);
foreach (var ddd in results)
{
    Console.WriteLine("数据"+ddd);
}
//Task.WhenAny
var results2 = await Task.WhenAny(t1,t2,t3);
var fasdf = results2.Result;
//FormResult
string ddddd = await Task.FromResult<string>("fdsfasd");

其他问题

在接口(interface)中不需要修饰async
yield return与async一起使用,返回值要设置成IAsyncEnumerable<>,不加Task,调用时在foreach前要加上await
异步,同步不要混用