日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C#多線程TPL模式高級用法探秘_C#教程

作者:.NET開發菜鳥 ? 更新時間: 2022-05-24 編程語言

一、引言

我們先來看下面的一個小示例:一個Winfrom程序,界面上有一個按鈕,有兩個異步方法,點擊按鈕調用兩個異步方法,彈出執行順序,代碼如下:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TPLDemoSln
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// 
        /// 按鈕點擊事件
        /// 
        /// 
        /// 
        private async void btnStart_Click(object sender, EventArgs e)
        {
            string i1 = await F1Async();
            MessageBox.Show("i1=" + i1);
            string i2 = await F2Async();
            MessageBox.Show("i2=" + i2);
        }

        /// 
        /// 異步方法F1
        /// 
        /// 
        private Task F1Async()
        {
            MessageBox.Show("F1 Start");
            return Task.Run(() => 
            {
                // 休眠1秒
                Thread.Sleep(1000);
                MessageBox.Show("F1 Run");
                return "F1";
            });
        }

        /// 
        /// 異步方法F2
        /// 
        /// 
        private Task F2Async()
        {
            MessageBox.Show("F2 Start");
            return Task.Run(() =>
            {
                // 休眠2秒
                Thread.Sleep(2000);
                MessageBox.Show("F2 Run");
                return "F2";
            });
        }
    }
}

在上面的代碼中,Task.Run()是用來把一個代碼段包裝為Task的方法,Run中委托的代碼體就是異步任務執行的邏輯,最后return返回值。

運行程序,可以得到如下的輸出順序:

F1 Start->F1 Run->i1=F1->F2 Start->F2 Run->i2=F2。

我們對按鈕事件進行修改,修改為下面的代碼,在看看執行順序:

/// 
/// 按鈕點擊事件
/// 
/// 
/// 
private async void btnStart_Click(object sender, EventArgs e)
{
    //string i1 = await F1Async();
    //MessageBox.Show("i1=" + i1);
    //string i2 = await F2Async();
    //MessageBox.Show("i2=" + i2);

    Task task1 = F1Async();
    Task task2 = F2Async();
    string i1 = await task1;
    MessageBox.Show("i1=" + i1);
    string i2 = await task2;
    MessageBox.Show("i2=" + i2);
}

再次運行程序,查看輸出順序:

F1 Start->F2 Start->F1 Run->i1=F1->F2 Run->i2=F2。

可以看出兩次的執行順序不一致。這是什么原因呢?

這是因為并不是到了await才開始執行Task異步任務,執行到Task task1=F1Async()這句代碼的時候,F1Async異步任務就開始執行了。同理,執行到下一句代碼就開始執行F2Async異步任務了。await是為了保證執行到這里的時候異步任務一定執行完。執行到await的時候,如果異步任務還沒有執行,那么就等待異步任務執行完。如果異步任務已經執行完了,那么就直接獲取異步任務的返回值。

我們上面解釋的原因是否正確呢?我們可以做下面的一個實驗,來驗證上面說的原因,我們把按鈕事件里面的await注釋掉,看看異步方法還會不會執行,代碼如下:

/// 
/// 按鈕點擊事件
/// 
/// 
/// 
private async void btnStart_Click(object sender, EventArgs e)
{
    //string i1 = await F1Async();
    //MessageBox.Show("i1=" + i1);
    //string i2 = await F2Async();
    //MessageBox.Show("i2=" + i2);

    Task task1 = F1Async();
    Task task2 = F2Async();
    // 并不是到了await才開始執行Task異步任務,這里是保證異步任務一定執行完
    // 代碼執行到這里的時候,如果沒有執行完就等待執行完,如果已經執行完,就直接獲取返回值
    // 這里注釋掉await代碼段,查看異步方法是否會執行
    //string i1 = await task1;
    //MessageBox.Show("i1=" + i1);
    //string i2 = await task2;
    //MessageBox.Show("i2=" + i2);
}

運行程序,發現異步方法還是會執行,這就說明我們上面解釋的原因是正確的。感興趣的可以使用Reflector反編譯查看內部實現的原理,主要是MoveNext()方法內部。

我們可以得到下面的結論:

  • 只要方法是Task類型的返回值,都可以用await來等待調用獲取返回值。
  • 如果一個返回Task類型的方法被標記了async,那么只要方法內部直接return T這個類型的實例就可以了。
  • 一個返回Task類型的方法如果沒有被標記為async,那么需要方法內部直接return一個Task的實例。

上面說的第二點看下面的代碼

/// 
/// 方法標記為async 直接返回一個int類型的數值即可
/// 
/// 
private async Task F3Async()
{
    return 2;
}

上面的第三點可以看下面的代碼:

/// 
/// 方法沒有被標記為async,直接返回一個Task
/// 
/// 
private Task F4Async()
{
    return Task.Run(() => 
    {
        return 2;
    });
}

二、TPL高級

我們做一些總結:

1、如果方法內部有await,則方法必須標記為async。await和async是成對出現的,只有await沒有async程序會報錯。只有async沒有await,程序會按照同步方法執行。

2、ASP.NET MVC中的Action方法和WinForm中的事件處理方法都可以標記為async,控制臺的Main()方法不能被標記為async。對于不能標記為async的方法怎么辦呢?我們可以使用Result屬性來獲取值,看下面代碼:

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace AsyncDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 實例化對象
            HttpClient client = new HttpClient();
            // 調用異步的Get方法
            Task taskMsg = client.GetAsync("http://www.baidu.com");
            // 通過Result屬性獲取返回值
            HttpResponseMessage msg = taskMsg.Result;
            Task taskRead = msg.Content.ReadAsStringAsync();
            string html = taskRead.Result;
            Console.WriteLine(html);
            Console.ReadKey();
        }
    }
}

不建議使用這種方式,這樣體現不出異步帶來的好處,而且使用Result屬性,有可能會帶來上下文切換造成的死鎖。

下面我們來看看創建Task的方法。

?1、如果返回值就是一個立即可以隨手得到的值,那么就用Task.FromResult()。看下面代碼:

static Task TestAsync()
{
    //return Task.Run(() => 
    //{
    //    return 5;
    //});

    // 簡便寫法
    return Task.FromResult(3);
}

2、如果是一個需要休息一會的任務(比如下載失敗則過5秒鐘后重試。主線程不休息,和Thread.Sleep不一樣),那么就用Task.Delay()。

3、Task.Factory.FromAsync()會把IAsyncResult轉換為Task,這樣APM風格的API也可以用await來調用。

4、編寫異步方法的簡化寫法。如果方法聲明為async,那么可以直接return具體的值,不用在創建Task,由編譯器創建Task,看下面的代碼:

static async Task Test2Async()
{
    // 復雜寫法
    //return await Task.Run(() => 
    //{
    //    return 5;
    //});

    // 下面是簡化寫法,直接返回
    return 6;
}

原文鏈接:https://www.cnblogs.com/dotnet261010/p/12343684.html

欄目分類
最近更新