[C#.NET][TPL] 等待所有任務完成

[C#.NET][TPL] 等待所有任務完成

在 TPL 裡 Task.WaitAll / Parallel.Invoke / Parallel.For / Parallel.ForEach 方法,都會等待所有工作完成 (也就是所謂的執行緒堵塞或稱為鎖定),才會再往下一行執行,若工作需要有順序就不要用它們。

PS.以下程式碼裡的迴圈沒有太大的意義,只是用來模擬耗時的工作。

內文章節:

調用 Task.WaitAll

調用 Parallel.Invoke

調用 Parallel.For


調用 Task.WaitAll

  • 因為 Task.WaitAll 會堵塞 UI 執行緒,並等待所有任務完成,所以我另外使用一個 Task 來啟用它。
  • 工作彼此之間沒有順序。

private void DoWork10()
{
    Task mainTask = new Task(() =>
    {
        Task t1 = new Task(() =>
        {
            Thread.CurrentThread.Name = "t1";
            ulong result = 0;

            for (int i = 0; i < 10000; i++)
            {
                result++;
                var str = result.ToString();
                Thread.Sleep(1);
            }
        });

        Task t2 = new Task(() =>
        {
            Thread.CurrentThread.Name = "t2";
            ulong result = 0;

            for (int i = 0; i < 10000; i++)
            {
                result++;
                var str = result.ToString();
                Thread.Sleep(1);
            }
        });

        t1.Start();
        t2.Start();

        Task.WaitAll(t1, t2);
        MessageBox.Show("DONE");
    });
    mainTask.Start();
}


Task.WaitAll 堵塞了執行緒,它會等待所有的工作完成才會執行下一行,它本身也是一條執行緒。

SNAGHTML1b235bfc

 

當然也可以設定等待的時間,以下程式碼表示等待(超時) 2 秒:


Task.WaitAll(new Task[] { t1, t2 }, 2000);

MessageBox.Show("DONE");

Console.WriteLine("t1:" + t1.Status);
Console.WriteLine("t2:" + t2.Status);

PS.解除執行緒堵塞,Task.Status 仍然等於 Running

你可能還會需要 Task.WaitAny 方法,只要任一工作完成就解除堵塞,目前還想不到有什麼工作流程可以使用它。


調用 Parallel.Invoke

繼續延用上一個程式碼。

  • 用法相當簡單只要把方法體丟進去就好,在這裡我使用 Lambda。
  • 因為 Parallel.Invoke 會堵塞 UI 執行緒,並等待所有任務完成,所以我另外使用一個 Task 來啟用它。
  • 這讓程式碼比上個例子看起來精簡了一些。
  • 若需要像上個例子一樣解除堵塞,它就不適合使用。
  • 任務沒有執行順序。

private void DoWork12()
{
    Task mainTask = new Task(() =>
    {
        Parallel.Invoke(() =>
        {
            Thread.CurrentThread.Name = "t1";
            ulong result = 0;

            for (int i = 0; i < 10000; i++)
            {
                result++;
                var str = result.ToString();
                Thread.Sleep(1);
            }
        }, () =>
        {
            Thread.CurrentThread.Name = "t2";
            ulong result = 0;

            for (int i = 0; i < 10000; i++)
            {
                result++;
                var str = result.ToString();
                Thread.Sleep(1);
            }
        });

        MessageBox.Show("DONE");
    });

    mainTask.Start();
}

由下圖得知,使用 Parallel.Invoke,TPL 內部會自動幫我們把程式碼切成多個 Task,並分派給 CPU,並且會等待所有 Task 都完成。

image


調用 Parallel.For

  • 平行運算的迴圈沒有執行順序,若要使用一定要確定工作流程沒有順序,若非得使用得謹慎處理。
  • 因為 Parallel.For 會堵塞 UI 執行緒,所以我另外使用一個 Task 來啟用它。

private void DoWork13()
{
    Task t = new Task(() =>
    {
        Parallel.For(1, 1000, _ =>
        {
            SpinWait.SpinUntil(() => false, 1000);
        });

        MessageBox.Show("DONE");
    });
    t.Start();
}

 

由下圖得知,Parallel.For 會將工作拆成多個 Task

image

 

Parallel.ForEach就不舉例了


更多的資料請參考:

http://msdn.microsoft.com/zh-tw/library/dd460705.aspx

http://msdn.microsoft.com/zh-tw/library/dd537610.aspx

 

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo