Task.Run

      在〈Task.Run〉中尚無留言

Task.Run 其實就是啟動一個新的執行緒,但這個方法有更進階的功能,蠻方便的。

XAML

為了測試 Task.Run,所以簡化 UI 的設計如下

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Name="lbl" Grid.Row="0"/>
        <Button Grid.Row="1" x:Name="btn" Click="btn_Click" Content="開始"/>
    </Grid>
</Window>

一般的執行緒

由底下的代碼可以看出,啟動新執行緒後,立即執行主執行緒的 MessageBox,過了 2 秒後才彈出新執行的MessageBox。

private void btn_Click(object sender, RoutedEventArgs e)
{
    Task t = Task.Run(
        () => {
            Thread.Sleep(2000);
            MessageBox.Show("Sleep", "新執行緒");
        }
    );
    MessageBox.Show("Hello", "主執行緒");
}

await

在 Task.Run() 之前加上 await 運算子後,就會等待 Task.Run()裏面的新執行緒執行完畢,才會再度執行主執行緒。神奇的是,視窗並不會因此停頓。

另外請注意,一但加上 await(等待,非同步),btn_Click 方法就要加上 async(同步),宣告此方法要處於同步不停頓的狀況。await 及 async 需同時存在。

private async void btn_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(
        () => { 
            Thread.Sleep(5000);
            MessageBox.Show(Thread.CurrentThread.Name,"test");
        }
    );
    MessageBox.Show("主執行緒","test");
}

返回值

如果新執行緒裏需要傳回某個值時,使用 return 即可,但 Task.Run 必需要有一個接收變數 result,如下所示。

private async void btn_Click(object sender, RoutedEventArgs e)
{
    Task<string> task = Task.Run(
        () => {
            MessageBox.Show("Sleep", "新執行緒");
            Thread.Sleep(1000); 
            return "Hello"; 
        }
    );
    string result = await task;
    MessageBox.Show(result, "主執行緒");
}

也可以寫成如下

private async void btn_Click(object sender, RoutedEventArgs e)
{
    string result = await Task.Run(
        () => {
            MessageBox.Show("Sleep", "新執行緒");
            Thread.Sleep(1000); 
            return "Hello"; 
        }
    ); 
    MessageBox.Show(result, "主執行緒");
}

方法

將新執行緒置於方法中,則方法必需傳回 Task<string> 物件。而方法中需 return Task.Run(),Run 方法裏的 Action 物件亦需 return “Hello”。

最後在主程式中使用 await Sleep()。

private async void btn_Click(object sender, RoutedEventArgs e)
{
    string s = await Sleep();
    MessageBox.Show(s,"主執行緒");
}
private Task<string> Sleep()
{
    return Task.Run(
        () => {
            MessageBox.Show("Sleep", "新執行緒");
            Thread.Sleep(1000);
            return "Hello";
        }
    );
}

新執行緒控制 UI 元件

在新執行緒裏是無法控制 UI 元件的,所以如果要在迴圈中改變元件,需使用 Dispatcher,如下

private async void btn_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(
        () => {
            for (var i = 0; i < 1000; i++)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => {
                    lbl1.Content = i.ToString();
                }));
                Thread.Sleep(1);
            }
        }
    );
    MessageBox.Show(index.ToString(), "主執行緒");
}

Dispatcher 陷井

Dispatcher 是等 UI 主執行緒有空時才會被執行的,如下可以驗証

private async void btn_Click(object sender, RoutedEventArgs e)
{
    var index = 0;
    await Task.Run(
        () => {
            for (var i = 0; i < 1000; i++)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
                {
                    index++;
                    lbl1.Content = i.ToString();
                }));
                //Thread.Sleep(1);
            }
        }
    );
    MessageBox.Show(index.ToString(), "主執行緒");
}

上述的迴圈跑了1000次,但 Dispatcher 其實才跑了 13~15次,實際次數依 CPU 速度而不同。

保証每次都會執行 Dispatcher

若要落實每個迴圈都跑一次 Dispatcher,則需在 Dispatcher 前加入 await,然後於 lambda 語法之前加入 async。底下的程式可以驗証 Dispatcher 確實跑了 1000 次。

private async void btn_Click(object sender, RoutedEventArgs e)
{
    var index = 0;
    await Task.Run(
        async () => {
            for (var i = 0; i < 1000; i++)
            {
                await Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
                {
                    index++;
                    lbl1.Content = i.ToString();
                }));
                //Thread.Sleep(1);
            }
        }
    );
    MessageBox.Show(index.ToString(), "主執行緒");
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *