await调用异步之后线程ID是否与调用异步之前线程ID一致(实战篇)

https://www.cnblogs.com/sjyforg/p/3949029.html
看到大佬的文章,小弟来测试下

1.winform应用程序

   private void button94_Click(object sender, EventArgs e)
    {
        TestAsync();
        
    }

    private async void TestAsync()
    {
           Console.WriteLine($"主线程调用异步之前ID{Thread.CurrentThread.ManagedThreadId}执行");
        await Test();

   Console.WriteLine($"异步后的线程ID{Thread.CurrentThread.ManagedThreadId}同步上下文:{SynchronizationContext.Current?.ToString()}开始执行");
    }

    private static Logger logger = LogManager.GetCurrentClassLogger();
 
    private async Task Test()
    {
        await GetHoliday("2021-01");
     
    }

    private async Task GetHoliday(string date)
    {
        WebClient client = new WebClient();
        client.Encoding = Encoding.UTF8;
        var url = $"https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?query={date}&resource_id=6018";
        var jsondata =await client.DownloadStringTaskAsync(url);
    }

输出

主线程调用异步之前ID1执行
异步后的线程ID1同步上下文:System.Windows.Forms.WindowsFormsSynchronizationContext开始执行

可以看出:调用之前ID为1,调用以后线程ID为1,同步上下文为 System.Windows.Forms.WindowsFormsSynchronizationContext.

经过多次测试:调用前后线程ID一样。

2.控制台程序

static void Main(string[] args)
{
    TestAsync();
    Console.ReadKey();
}

private static async void TestAsync()
{
    Console.WriteLine($"调用异步之前ID{Thread.CurrentThread.ManagedThreadId}执行");

    await Test();

    Console.WriteLine($"异步后的线程ID{Thread.CurrentThread.ManagedThreadId}同步上下文:{SynchronizationContext.Current?.ToString()}开始执行");
}

private static Logger logger = LogManager.GetCurrentClassLogger();

private static async Task Test()
{
    try
    {
        await GetHoliday("2021-01");
        
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }
}

private static async Task GetHoliday(string date)
{
    WebClient client = new WebClient();
    client.Encoding = Encoding.UTF8;
    var url = $"https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?query={date}&resource_id=6018";
    var jsondata = await client.DownloadStringTaskAsync(url);
}

输出日志:
在这里插入图片描述
可以看出:调用之前ID为1,调用以后线程ID为11,同步上下文为 null.

经过多次测试:调用前后线程ID不一样。

3.Asp.net WebApi

public async Task<string> getPATData([FromUri]PATQuery patQuery)
{
    try
    {
        Logs.Info($"主线程调用异步之前ID{Thread.CurrentThread.ManagedThreadId}执行");

        var data = new
        {
            list = await patService.GetPAT(patQuery),
            total = patQuery.records,
        };

        Logs.Info($"异步后的线程ID{Thread.CurrentThread.ManagedThreadId}同步上下文:{SynchronizationContext.Current?.ToString()}开始执行");

        return data.ToJson();
    }
    catch (Exception e)
    {
        Logs.LogWriter(e.GetOriginalException());
    }
    return string.Empty;
}

输出日志:

2022-03-05 14:07:23.2669 INFO 主线程调用异步之前ID12执行
2022-03-05 14:07:24.0974 INFO 异步后的线程ID11同步上下文:System.Web.LegacyAspNetSynchronizationContext开始执行
2022-03-05 14:07:24.6551 INFO 主线程调用异步之前ID9执行
2022-03-05 14:07:25.4546 INFO 异步后的线程ID9同步上下文:System.Web.LegacyAspNetSynchronizationContext开始执行

可以看出:

第一次调用之前ID为12,调用以后线程ID为11,同步上下文为 System.Web.LegacyAspNetSynchronizationContext

第二次调用之前ID为9,调用以后线程ID为9,同步上下文为 System.Web.LegacyAspNetSynchronizationContext

经过多次测试:调用前后线程ID可能一样可能不一样。

第二次调用前后线程ID一样,可能的原因:使用 await 关键字,执行到真正的async方法(比如:ToListAsync()),当前线程立马被释放回线程池,线程的上下文信息会被保存。当 async 中的异步任务执行完成后,会获取当初调用者所在线程的上下文信息(如果当初调用者所在线程没有释放回线程池,上下文信息可以获取到)。 真正的async方法执行很快,线程还没释放回线程池。

结论:

引用大佬的结论,await之后的线程依据SynchronizationContext在不同环境中的不同定义而产生不同的结果。所以「await 之后不会开启新的线程(await 从来不会开启新的线程)」的肯定句式改成「await 之后会开启新的线程吗? Maybe」这样的句式更加合适些。


版权声明:本文为weixin_46879188原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。