溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C#怎么使用Task實現執(zhí)行并行任務

發(fā)布時間:2023-04-04 14:25:53 來源:億速云 閱讀:159 作者:iii 欄目:開發(fā)技術

這篇“C#怎么使用Task實現執(zhí)行并行任務”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“C#怎么使用Task實現執(zhí)行并行任務”文章吧。

    一、Task執(zhí)行并行任務的原理

    使用Task執(zhí)行并行任務的原理是將任務分成多個小塊,每個小塊都可以在不同的線程上運行。然后,使用Task.Run方法將這些小塊作為不同的任務提交給線程池。線程池會自動管理線程的創(chuàng)建和銷毀,并根據系統(tǒng)資源的可用情況來自動調整線程數量,從而實現最大化利用CPU資源的效果。

    二、5個示例展示

    示例1

    下面是一個簡單的示例,展示如何使用Task來執(zhí)行并行任務:

    void Task1()
    {
        // 創(chuàng)建任務數組
        var tasks = new Task[10];
    
        for (var i = 0; i < tasks.Length; i++)
        {
            var taskId = i + 1;
    
            // 使用Task.Run方法提交任務
            tasks[i] = Task.Run(() =>
            {
                Console.WriteLine("任務 {0} 運行在線程 {1} 中", taskId, Task.CurrentId);
                // 執(zhí)行任務邏輯
            });
        }
    
        // 等待所有任務完成
        Task.WaitAll(tasks);
    
        Console.WriteLine("所有任務運行完成。");
        Console.ReadKey();
    }

    在這個示例中,我們創(chuàng)建了一個長度為10的任務數組,然后使用Task.Run方法將每個任務提交給線程池。在任務執(zhí)行時,使用Task.CurrentId屬性獲取當前任務的ID,并打印出來以方便觀察。最后,我們使用Task.WaitAll方法等待所有任務完成并打印出一條完成信息。

    運行的結果:

    任務 3 運行在線程 11 中
    任務 4 運行在線程 12 中
    任務 8 運行在線程 16 中
    任務 1 運行在線程 9 中
    任務 2 運行在線程 10 中
    任務 5 運行在線程 13 中
    任務 6 運行在線程 14 中
    任務 7 運行在線程 15 中
    任務 9 運行在線程 17 中
    任務 10 運行在線程 18 中
    所有任務運行完成。

    值得注意的是,在實際開發(fā)中,需要根據具體情況來評估任務的大小和數量,以確保并行任務的效率和可靠性。

    示例2

    另一個使用Task的示例是計算斐波那契數列。我們可以將斐波那契數列的每一項看成一個任務,然后使用Task.WaitAll方法等待所有任務完成。

    void Task2()
    {
        static long Fib(int n)
        {
            if (n is 0 or 1)
            {
                return n;
            }
            else
            {
                return Fib(n - 1) + Fib(n - 2);
            }
        }
    
        const int n = 10; // 計算斐波那契數列的前n項
    
        var tasks = new Task<long>[n];
    
        for (var i = 0; i < n; i++)
        {
            var index = i; // 需要在閉包內使用循環(huán)變量時需要賦值給另外一個變量
    
            if (i < 2)
            {
                tasks[i] = Task.FromResult((long)i);
            }
            else
            {
                tasks[i] = Task.Run(() => Fib(index));
            }
        }
    
        // 等待所有任務完成
        Task.WaitAll(tasks);
    
        // 打印結果
        for (var i = 0; i < n; i++)
        {
            Console.Write("{0} ", tasks[i].Result);
        }
    
        Console.ReadKey();
    }

    在這個示例中,我們使用Task數組來存儲所有的任務。如果需要計算的是前兩項,則直接使用Task.FromResult創(chuàng)建完成任務,否則使用Task.Run方法創(chuàng)建異步任務并調用Fib方法計算結果。在等待所有任務完成后,我們遍歷Task數組,并使用Task.Result屬性獲取每個任務的結果并打印出來。

    運行的結果:

    0 1 1 2 3 5 8 13 21 34

    需要注意的是,在創(chuàng)建異步任務時,由于循環(huán)變量在閉包內的值是不確定的,因此需要將其賦值給另外一個變量,并在閉包內使用該變量。否則,所有任務可能會使用同一個循環(huán)變量的值,導致結果錯誤。

    示例3

    除了使用Task數組存儲所有任務,還可以使用Task.Factory.StartNew方法創(chuàng)建并行任務。這個方法與Task.Run方法類似,都可以創(chuàng)建異步任務并提交給線程池。

    void Task3()
    {
        long Factorial(int n)
        {
            if (n == 0) return 1;
            return n * Factorial(n - 1);
        }
    
        const int n = 5; // 計算階乘的數
    
        var task = Task.Factory.StartNew(() => Factorial(n));
    
        Console.WriteLine("計算階乘...");
    
        // 等待任務完成
        task.Wait();
    
        Console.WriteLine("{0}! = {1}", n, task.Result);
        Console.ReadKey();
    }

    在這個示例中,我們使用Task.Factory.StartNew方法創(chuàng)建一個計算階乘的異步任務,并等待任務完成后打印結果。

    運行結果:

    計算階乘...
    5! = 120

    需要注意的是,盡管Task.Run和Task.Factory.StartNew方法都可以創(chuàng)建異步任務,但它們的行為略有不同。特別是,Task.Run方法總是使用TaskScheduler.Default作為任務調度器,而Task.Factory.StartNew方法可以指定任務調度器、任務類型和其他選項。因此,在選擇使用哪種方法時,需要根據具體情況進行評估。

    示例4

    另一個使用Task的示例是異步讀取文件。在這個示例中,我們使用Task.FromResult方法創(chuàng)建一個完成任務,并將文件內容作為結果返回。

    void Task4()
    {
        const string filePath = "test.txt";
    
        var task = Task.FromResult(File.ReadAllText(filePath)); // 只是方便舉例,更好的代碼應該是:File.ReadAllTextAsync(filePath); 
    
        Console.WriteLine("讀取文件內容...");
    
        // 等待任務完成
        task.Wait();
    
        Console.WriteLine("文件內容: {0}", task.Result);
        Console.ReadKey();
    }

    在這個示例中,我們使用Task.FromResult方法創(chuàng)建一個完成任務,并通過File.ReadAllText方法讀取文件內容并將其作為結果返回。在等待任務完成后,我們可以通過調用Task.Result屬性來獲取任務的結果。

    文中記事本請隨意創(chuàng)建

    需要注意的是,在實際開發(fā)中,如果需要處理大型文件或需要執(zhí)行長時間的I/O操作,則應該使用異步代碼來避免阻塞UI線程。例如,在讀取大型文件時,我們可以使用異步代碼來避免阻塞UI線程,從而提高應用程序的性能和響應速度。

    示例5

    最后一個示例是使用Task和async/await實現異步任務。在這個示例中,我們將一個耗時的操作封裝為異步方法,并使用async/await關鍵字來等待該操作完成。

    async Task Task5()
    {
        async Task<string> LongOperationAsync()
        {
            // 模擬耗時操作
            await Task.Delay(TimeSpan.FromSeconds(3));
    
            return "完成";
        }
    
        Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}開始耗時操作...");
    
        // 等待異步方法完成
        var result = await LongOperationAsync();
    
        Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}耗時操作完成: {result}");
        Console.ReadKey();
    }

    在這個示例中,我們使用async/await關鍵字將LongOperationAsync方法聲明為異步方法,并使用await關鍵字等待Task.Delay操作完成。在主程序中,我們可以使用await關鍵字等待LongOperationAsync完成并獲取其結果。

    2023-03-28 20:54:09.111開始耗時操作...
    2023-03-28 20:54:12.143耗時操作完成: 完成

    需要注意的是,在使用async/await關鍵字時,應該避免在異步方法內部使用阻塞線程的操作,否則可能會導致UI線程被阻塞。如果必須執(zhí)行阻塞操作,可以將其放在不同的線程上執(zhí)行,或者使用異步IO操作來避免阻塞線程。

    三、使用async/await關鍵字注意

    在使用async/await關鍵字時,還有一些細節(jié)需要注意,再給出兩個示例。

    示例1

    示例代碼如下所示:

    async Task Task6()
    {
        async Task<string> LongOperationAsync(int id)
        {
            // 模擬耗時操作
            await Task.Delay(TimeSpan.FromSeconds(1 + id));
    
            return $"{DateTime.Now:ss.fff}完成 {id}";
        }
    
        Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}開始耗時操作...");
    
        // 等待多個異步任務完成
        var task1 = LongOperationAsync(1);
        var task2 = LongOperationAsync(2);
        var task3 = LongOperationAsync(3);
    
        var results = await Task.WhenAll(task1, task2, task3);
        var resultStr = string.Join(",", results);
    
        Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}耗時操作完成: {resultStr}");
        Console.ReadKey();
    }

    在這個示例中,我們使用Task.WhenAll方法等待多個異步任務完成,并使用Join方法將所有任務的結果連接起來作為最終結果。

    2023-03-28 21:15:42.855開始耗時操作...
    2023-03-28 21:15:46.894耗時操作完成: 44.888完成 1,45.883完成 2,46.893完成 3

    示例2

    另一個需要注意的問題是,在使用async/await關鍵字時,應該盡可能避免使用ConfigureAwait(false)方法。這個方法可以讓異步操作不必恢復到原始的SynchronizationContext上,從而減少線程切換的開銷和提高性能。

    然而,在某些情況下,如果在異步操作完成后需要返回到原始的SynchronizationContext上,使用ConfigureAwait(false)會導致調用者無法正確處理結果。因此,建議僅在確定不需要返回到原始的SynchronizationContext上時才使用ConfigureAwait(false)方法。

    示例代碼: 假設我們有一個控制臺應用程序,其中有兩個異步方法:MethodAAsync()和MethodBAsync()。MethodAAsync()會等待1秒鐘,然后返回一個字符串。MethodBAsync()會等待2秒鐘,然后返回一個字符串。代碼如下所示:

    async Task<string> MethodAAsync()
    {
        await Task.Delay(1000);
        return $"{DateTime.Now:ss.fff}>Hello";
    }
    
    async Task<string> MethodBAsync()
    {
        await Task.Delay(2000);
        return $"{DateTime.Now:ss.fff}>World";
    }

    現在,我們想要同時調用這兩個方法,并將它們的結果合并成一個字符串。我們可以像下面這樣編寫代碼:

    async Task<string> CombineResultsAAsync()
    {
        var resultA = await MethodAAsync();
        var resultB = await MethodBAsync();
        return $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}: {resultA} | {resultB}";
    }

    這個代碼看起來非常簡單明了,但是它存在一個性能問題。當我們調用CombineResultsAAsync()方法時,第一個await操作將使執(zhí)行上下文切換回原始SynchronizationContext(即主線程),因此我們的異步操作將在UI線程上運行。由于我們要等待1秒鐘才能從MethodAAsync()中返回結果,因此UI線程將被阻塞,直到異步操作完成并且結果可用為止。

    這種情況下,我們可以使用ConfigureAwait(false)方法來指定不需要保留當前上下文的線程執(zhí)行狀態(tài),從而讓異步操作在一個線程池線程上運行。這可以通過下面的代碼實現:

    async Task<string> CombineResultsBAsync()
    {
        var resultA = await MethodAAsync().ConfigureAwait(false);
        var resultB = await MethodBAsync().ConfigureAwait(false);
        return $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}: {resultA} | {resultB}";
    }

    通過使用ConfigureAwait(false)方法,我們告訴異步操作不需要保留當前上下文的線程執(zhí)行狀態(tài),這樣異步操作就會在一個線程池線程上運行,而不是在UI線程上運行。這樣做可以避免一些潛在的性能問題,因為我們的UI線程不會被阻塞,并且異步操作可以在一個新的線程池線程上運行。

    以上就是關于“C#怎么使用Task實現執(zhí)行并行任務”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI