天天看點

.Net中async、await的用法及其與Task的關系

本文中使用的示例代碼都是在WinForm中編寫調試的,我根據是否使用await處理Task、調用異步方法/非異步方法、方法的傳回值為Task/Task<Student>,做了8種情況的分析和總結。

public class CommonUtil
{
    public static async Task<Student> AsyncGetStudentInfoHasReturn(string sname)
    {
        string name = $"Handle:{sname}";
        var result = await Task.Run<Student>(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("Sub01-Start【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            Thread.Sleep(4000);
            Console.WriteLine("Sub01-End【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            return new Student() { Id = "01", SName = sname, Age = 12 };
        });
        Console.WriteLine("Sub01-await之後【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
        result = await Task.Run<Student>(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("Sub02-Start【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            Thread.Sleep(4000);
            Console.WriteLine("Sub02-End【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            return new Student() { Id = "01", SName = sname, Age = 12 };
        });
        Console.WriteLine("Sub02-await之後【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
        return result;
    }

    public static async Task AsyncGetStudentInfoNoReturn(string sname)
    {
        string name = $"Handle:{sname}";
        await Task.Run(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("Sub01-Start【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            Thread.Sleep(4000);
            Console.WriteLine("Sub01-End【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
        });
        Console.WriteLine("Sub01-await之後【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
        await Task.Run(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("Sub02-Start【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            Thread.Sleep(4000);
            Console.WriteLine("Sub02-End【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
        });
        Console.WriteLine("Sub02-await之後【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    }

    public static Task<Student> GetStudentInfoHasReturn(string sname)
    {
        string name = $"Handle:{sname}";
        Task<Student> task = Task.Run<Student>(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("Sub-Start【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            Thread.Sleep(4000);
            Console.WriteLine("Sub-End【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            return new Student() { Id = "01", SName = sname, Age = 12 };
        });
        Console.WriteLine("Sub-TaskRun之後【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
        return task;
    }

    public static Task GetStudentInfoNoReturn(string sname)
    {
        string name = $"Handle:{sname}";
        Task task = Task.Run(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("Sub-Start【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
            Thread.Sleep(4000);
            Console.WriteLine("Sub-End【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
        });
        Console.WriteLine("Sub-TaskRun之後【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
        return task;
    }
}
           

一、使用await調用傳回值為Task<Student>的異步方法

/// <summary>
/// 使用await調用傳回值為Task<Student>的異步方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnInvokeAsyncHasResultUseAwait_Click(object sender, EventArgs e)
{
    Console.WriteLine("【1】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    var task01 = CommonUtil.AsyncGetStudentInfoHasReturn("AAA");
    Thread.Sleep(2000);
    Console.WriteLine("【2】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    await task01;//await時界面不卡頓
    Console.WriteLine("【3】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    Student student01 = task01.Result;//await之後擷取傳回值時無需再等待
    Console.WriteLine("【4】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
}
           
.Net中async、await的用法及其與Task的關系

二、不使用await調用傳回值為Task<Student>的異步方法

/// <summary>
/// 不使用await調用傳回值為Task<Student>的異步方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnInvokeAsyncHasResultNoAwait_Click(object sender, EventArgs e)
{
    Console.WriteLine("【1】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    var task01 = CommonUtil.AsyncGetStudentInfoHasReturn("AAA");
    Thread.Sleep(2000);
    Console.WriteLine("【2】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    Student student01 = task01.Result;//調用有傳回值的異步方法時,省略await直接用task01.Result擷取傳回值時會一直等待
    Console.WriteLine("【3】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
}
           
.Net中async、await的用法及其與Task的關系

三、使用await調用傳回值為Task的異步方法

/// <summary>
/// 使用await調用傳回值為Task的異步方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnInvokeAsyncNoResultUseAwait_Click(object sender, EventArgs e)
{
    Console.WriteLine("【1】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    var task01 = CommonUtil.AsyncGetStudentInfoNoReturn("AAA");
    Thread.Sleep(20000);
    Console.WriteLine("【2】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    await task01;//await時界面不卡頓,必須等CommonUtil.AsyncGetStudentInfoNoReturn("AAA")執行完最後一行代碼才算await執行完畢
    Console.WriteLine("【3】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
}
           
.Net中async、await的用法及其與Task的關系

四、不使用await調用傳回值為Task的異步方法

/// <summary>
/// 不使用await調用傳回值為Task的異步方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnInvokeAsyncNoResultNoAwait_Click(object sender, EventArgs e)
{
    Console.WriteLine("【1】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    var task01 = CommonUtil.AsyncGetStudentInfoNoReturn("AAA");//執行時界面不卡頓
    Thread.Sleep(20000);
    Console.WriteLine("【2】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
}
           
.Net中async、await的用法及其與Task的關系

五、使用await調用傳回值為Task<Student>的方法 

/// <summary>
/// 使用await調用傳回值為Task<Student>的方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnInvokeTaskHasResultUseAwait_Click(object sender, EventArgs e)
{
    Console.WriteLine("【1】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    Task<Student> task = CommonUtil.GetStudentInfoHasReturn("AAA");
    Thread.Sleep(2000);
    Console.WriteLine("【2】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    await task;//await時界面不卡頓
    Console.WriteLine("【3】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    Student student02 = task.Result;//await之後擷取傳回值時無需再等待
    Console.WriteLine("【4】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
}
           
.Net中async、await的用法及其與Task的關系

六、不使用await調用傳回值為Task<Student>的方法  

/// <summary>
/// 不使用await調用傳回值為Task<Student>的方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnInvokeTaskHasResultNoAwait_Click(object sender, EventArgs e)
{
    Console.WriteLine("【1】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    Task<Student> task = CommonUtil.GetStudentInfoHasReturn("AAA");
    Thread.Sleep(2000);
    Console.WriteLine("【2】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    Student student01 = task.Result;//通過task.Result擷取傳回值時界面會卡頓
    //Student student02 = task.GetAwaiter().GetResult();//通過task.GetAwaiter().GetResult()擷取傳回值時界面會卡頓
    Console.WriteLine("【3】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
}
           
.Net中async、await的用法及其與Task的關系

 七、使用await調用傳回值為Task的方法 

/// <summary>
/// 使用await調用傳回值為Task的方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnInvokeTaskNoResultUseAwait_Click(object sender, EventArgs e)
{
    Console.WriteLine("【1】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    Task task = CommonUtil.GetStudentInfoNoReturn("AAA");
    Thread.Sleep(2000);
    Console.WriteLine("【2】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    await task;//await時界面不卡頓,必須等CommonUtil.GetStudentInfoNoReturn("AAA")執行完最後一行代碼才算await執行完畢
    Console.WriteLine("【3】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
}
           
.Net中async、await的用法及其與Task的關系

八、不使用await調用傳回值為Task的方法

/// <summary>
/// 不使用await調用傳回值為Task的方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnInvokeTaskNoResultNoAwait_Click(object sender, EventArgs e)
{
    Console.WriteLine("【1】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
    var task01 = CommonUtil.GetStudentInfoNoReturn("AAA");//執行時界面不卡頓
    Thread.Sleep(2000);
    Console.WriteLine("【2】Main【ThreadId=" + Thread.CurrentThread.ManagedThreadId + "】:" + DateTime.Now);
}
           
.Net中async、await的用法及其與Task的關系

九、總結 

1.1、async方法中可以調用有傳回值的async方法、沒傳回值的async方法、有傳回值的非async方法、沒傳回值的非async方法

1.2、非async方法中可以調用沒傳回值的async方法、有傳回值的非async方法、沒傳回值的非async方法

1.3、非async方法中不能調用有傳回值的async方法,因為有傳回值的async方法必須使用await擷取傳回值,而非async方法中不能使用await

2.1、async方法有傳回值時必須使用await讓主線程等待任務執行完畢并擷取傳回值

2.2、async方法沒傳回值時可以使用await讓主線程等待任務執行完畢,也可以不使用await在主線程空閑時再等待任務執行完畢

2.3、主線程什麼時候是空閑呢?

3.1、使用await擷取async方法、非async方法的傳回值時界面不卡頓

3.2、使用task.Result擷取非async方法的傳回值時界面會卡頓

3.3、使用task.Result無法擷取async方法的傳回值,必須使用await擷取async方法的傳回值