2012年11月21日水曜日

入力パラメータがないラムダ式の右辺で外部変数を使用する際の注意

Task.Factory.StartNew を使って、別スレッドで処理させるときに、デリゲートの引数を渡す方法と、直接外部変数を渡す方法があります。
※ここでの外部変数argとは、MainWindowメソッドのローカル変数argのことです。
デリゲートの引数を渡す方法は、このステートメントが実行された時点での外部変数argの値が使われます。
Task.Factory.StartNew<string>(obj => ReturnArg((string)obj), arg);
しかし、直接ローカル変数を渡す方法では、ReturnArg メソッドが実行された時点での外部変数argの値が使われます。
Task.Factory.StartNew<string>(() => ReturnArg(arg));
Task.Factory.StartNewで生成されたスレッドはいずれも、最初は待機状態になっていて、しばらくたってからReturnArgメソッドが実行されます。

サンプルを実行すると、下のようになります。



ソースの記述順では、
noArgTask1 → oneArgTask1 → noArgTask2 → oneArgTask2
の順にタスクが生成されますが、各タスクの実行順は、
oneArgTask1 → oneArgTask2 → noArgTask1 → noArgTask2
になってます。また、4つのタスクの呼び出し元のスレッドは、いずれのタスクも実行される前に、各タスクの完了待ち状態になっています。 ReturnArgメソッドの実行時点での外部変数argの値を参照するタスク noArgTask1 と noArgTask2 の戻り値は、Step1、Step3ではなく、いずれもStep4 です。

MainWindow.xaml

<Window x:Class="ActionTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="150" Width="280">
    <Grid>
        <TextBlock Name="textBlock1" />
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;

namespace ActionTest
{
    public partial class MainWindow : Window
    {
        private Stopwatch sw = new Stopwatch();

        public MainWindow()
        {
            InitializeComponent();

            string arg;

            sw.Start();

            arg = "Step1";
            Task<string> noArgTask1 = Task.Factory.StartNew<string>(() => ReturnArg(arg));
            arg = "Step2";
            Task<string> oneArgTask1 = Task.Factory.StartNew<string>(obj => ReturnArg((string)obj), arg);
            arg = "Step3";
            Task<string> noArgTask2 = Task.Factory.StartNew<string>(() => ReturnArg(arg));
            arg = "Step4";
            Task<string> oneArgTask2 = Task.Factory.StartNew<string>(obj => ReturnArg((string)obj), arg);

            TimeSpan lastTimeSpan = sw.Elapsed;
            this.textBlock1.Text =
                "noArgTask1 = " + noArgTask1.Result + "\n" +
                "oneArgTask1 = " + oneArgTask1.Result + "\n" +
                "noArgTask2 = " + noArgTask2.Result + "\n" +
                "oneArgTask2 = " + oneArgTask2.Result + "\n" +
                "Last = " + lastTimeSpan;
        }

        private string ReturnArg(string arg)
        {
            return this.sw.Elapsed + " " + arg;
        }
    }
}

0 件のコメント:

コメントを投稿