C#コントロールの複数スレッドアクセス問題

C#のアプリ開発で嵌ったメモ書きです。

C#でフォームのコントロールを扱うとき、同一スレッドからアクセスする必要があります。
たとえば、以下のようにDataGridViewに別スレッドからアクセスすることを考えて見ます。

    public partial class Form1 : Form
    {
        Task task;
        object syncObj = new object();

        public Form1()
        {
            InitializeComponent();

            TaskFactory ts = new TaskFactory();
            task = ts.StartNew(() => {
                lock (syncObj)
                {
                    if (dataGridView1 != null)
                    {
                        for (int i = 0; i < dataGridView1.RowCount; i++)
                        {
                            dataGridView1[0, i].Tag = DateTime.Now;
                        }
                    }
                }
            });
        }
    }

一見すると正常なコードに見えます。
異なるスレッドからのアクセスを考慮してDataGridViewには排他ロックをかけています。
オブジェクトのnullチェックや配列のサイズチェックもしっかり行っているため、問題無いように見えます。

しかし、これを数百回繰り返すと稀にNullReferenceExceptionやArgumentOutOfRangeExceptionが発生することがあります。

これはどういうことでしょうか。

異なるスレッドからアクセスすると、データの整合性が取れなくなる場合があるからです。
メインスレッドからDataGridViewオブジェクトの中身を書き換えている途中で
別のスレッドからアクセスされるためです。

したがって、前述このようなコードはメインスレッドからアクセスすることで発生しなくなります。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            if (dataGridView1 != null)
            {
                for (int i = 0; i < dataGridView1.RowCount; i++)
                {
                    dataGridView1[0, i].Tag = DateTime.Now;
                }
            }
        }
    }

この例はあまり有用とは言えませんが、単純にメインスレッドで単純にアクセスさせるようにした例です。
これで前述の例外は発生しなくなります。

このようなマルチスレッド系のバグはなかなか気づきにくいため注意すべきです。