异步编程理解

1.async await 原理

把异步方法解析成一个类,把内部异步函数分块,通过操作状态机,排布执行计划。

线程使用:await调用的等待期间,.NET会把当前线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取一个线程执行后续代码,即await不会阻塞线程,await函数执行完成会回调线程池唤起新线程拿到返回结果。(Thread.CurrentThread.ManagedThreadId获取当前线程Id)

 

测试线程调度code:

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
string fileName = @"C:\Users\qwei\source\1.txt";
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
    stringBuilder.Append(i.ToString() + "  helloya");
}
await File.WriteAllTextAsync(fileName, stringBuilder.ToString());

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
result:

 

2.异步编程不等于多线程编程

异步方法的代码并不会自动在新线程中执行,除非把代码放到新线程中执行。

放到新线程执行code:

internal class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("before," + Thread.CurrentThread.ManagedThreadId);
        double a = await CalcAsync(5000);
        Console.WriteLine(a.ToString());
        Console.WriteLine("after," + Thread.CurrentThread.ManagedThreadId);

    }
    static async Task<double> CalcAsync(int num)
    {
        Console.WriteLine("CalcAsyncBefore," + Thread.CurrentThread.ManagedThreadId);
        return await Task.Run(() =>
                                  {
                                      Console.WriteLine("CalcAsync," + Thread.CurrentThread.ManagedThreadId);
                                      double result = 0;
                                      Random random = new Random();
                                      for (int i = 0; i < num * num; i++)
                                      {
                                          result += random.NextDouble();
                                      }
                                      return result;
                                  });
    }
}

 

 

result:

 

3.没有async的异步方法

Q:什么情况用async呢?

A:如果一个异步函数只是对别的异步函数调用的转发,并没有太多复杂的逻辑,那么可以去掉async关键字;若需要对内部调用的异步函数结果做处理的情况,建议使用async。

返回值为Task的不一定都要标注async,标注async可以让我们可以更方便的await,举例如下:

///直接转发内部调用的异步方法结果。使用async,多拆装一次
static async Task<string> ReadAsync(int num)
{
    switch (num)
    {
        case 0:
            string s = await File.ReadAllTextAsync(@"C:\Users\qwei\source\1.txt");
            return s;
        case 1:
            string s1 = await File.ReadAllTextAsync(@"C:\Users\qwei\source\2.txt");
            return s1;
        default:
            throw new ArgumentException();
    }
}

///直接转发内部调用的异步方法结果,不使用async
static Task<string> ReadWithoutAsync(int num)
{
    switch (num)
    {
        case 0:
            return File.ReadAllTextAsync(@"C:\Users\qwei\source\1.txt");
        case 1:
            return File.ReadAllTextAsync(@"C:\Users\qwei\source\2.txt");
        default:
            throw new ArgumentException();
    }
}

///需要对调用的异步方法做处理,则需要使用async
static async Task<string> ReadAsyncNew(int num)
{
    switch (num)
    {
        case 0:
            string s = await File.ReadAllTextAsync(@"C:\Users\qwei\source\1.txt");
            return s + "addtional";
        case 1:
            string s1 = await File.ReadAllTextAsync(@"C:\Users\qwei\source\2.txt");
            return s1 + "addtional";
        default:
            throw new ArgumentException();
    }
}
async方法缺点:

1.异步方法会生成一个类,运行效率没有普通方法高;

2.可能会占用非常多的线程;

4.异步编程不要用sleep

Thread.Sleep()会阻塞调用线程,在异步方法中可能会阻塞主线程,建议使用await Task.Delay()

样例code:

internal class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("Hello World!");
        Console.WriteLine(await DownloadHTML(2, "https://www.baidu.comsssssss"));
    }

    public static async Task<string> DownloadHTML(int retryCount, string webUrl)
    {
        using (HttpClient client = new HttpClient())
        {
            try
            {
                string strWeb = await client.GetStringAsync(webUrl);
                return strWeb;
            }
            catch (Exception ex)
            {
                Console.WriteLine("下载失败,重试中");
                retryCount--;
                if (retryCount == 0)
                {
                    throw new Exception("下载失败");
                }
                await Task.Delay(500);
                return await DownloadHTML(retryCount, webUrl);
            }
        }
    }
}
 

5.CancellationToken

使用场景:

需要提前终止任务的场景,比如:请求超时、用户取消请求

cancellationToken 结构体 重点属性:

None:空

bool IsCancellationRequested : 是否取消

ThrowIfCancellationRequested() : 如果任务被取消,执行到这句话是抛出异常

为下载一个网站N次添加CancellationToken,样例code:

internal class Program
{
    static async Task Main(string[] args)
    {
        CancellationTokenSource cts= new CancellationTokenSource();
        cts.CancelAfter(TimeSpan.FromSeconds(5));
        CancellationToken ct= cts.Token;
        await Download2Async("https://www.baidu.com", 100, ct);
        await Download2OtherAsync("https://www.baidu.com",100,ct);
    }

    ///终止下一次请求
    static async Task Download2Async(string url, int n, System.Threading.CancellationToken cn)
    {
        using (HttpClient client = new HttpClient())
        {
            for (int i = 0; i < n; i++)
            {
                string html= await client.GetStringAsync(url);
                if (!string.IsNullOrEmpty(html))
                {
                    Console.WriteLine($"{DateTime.Now}:{html.Substring(10)}");
                }
                //func 1:
                /*if (cn.IsCancellationRequested)
                    {
                        Console.WriteLine("request had been canceled");
                        break;
                    }*/
                //func 2:
                cn.ThrowIfCancellationRequested();
            }
        }
    }

    //请求后终止
    static async Task Download2OtherAsync(string url, int n, System.Threading.CancellationToken ct)
    {
        using (HttpClient client = new HttpClient())
        {
            for (int i = 0; i < n; i++)
            {
                var response = await client.GetAsync(url, ct);
                string html = await response.Content.ReadAsStringAsync();
                Console.WriteLine($"{DateTime.Now}:{html.Substring(20)}");
            }
        }
    }
}

 

 

6.Task类重要函数 WhenAny和WhenAll

WhenAny:(IEnumerable<Task> tasks),任何一个Task完成,Task就完成;

WhenAll:(IEnumerable<Task> tasks),所有Task完成,Task才完成,多用于等待多个任务执行结束,但是不在乎他们的执行顺序的场景;

FromResult()创建普通数值的Task对象;

获取某个文件夹内所有文件长度总和,样例code:

internal class Program
{
    static async Task Main(string[] args)
    {
        string[] files = Directory.GetFiles(@"C:\NetCore\temp");
        Task<int>[] countsTask=new Task<int>[files.Length];
        for (int i = 0; i < files.Length; i++)
        {
            string filename=files[i];
            Task<int> t = ReadCharsCount(filename);
            countsTask[i] = t;
        }

        int[] counts =await Task.WhenAll(countsTask);
        int C = counts.Sum();
        Console.WriteLine(C);
    }

    private static async Task<int> ReadCharsCount(string filename)
    {
        string s = await File.ReadAllTextAsync(filename);
        return s.Length;
    }
}

 

 

 

7.异步编程其他问题

a. interface中方法或者抽象方法不能修饰为async(在实现类中加async);

b. yield return不仅能够简化数据返回,而且可以让数据处理”流水线化“,提升性能;

 

 

 

------------------------完结~~撒花~~~❀❀❀❀❀❀❀------------------------

posted @ 2023-08-04 15:53  T丶MELO  阅读(29)  评论(0编辑  收藏  举报