c# 대이터 래이스를 막는 lock의 유효 범위

아래의 코드는 대이터 래이스를 일어나게 하는 예제다. start debugging을 하면 오류로 인해 실행되지 않고 start without debugging을 하면 대이터 래이스를 확인할 수 있다.

class TestClass
{
    public List<int> Ints = new();
}

TestClass TestClass1 = new();

private void button1_Click(object sender, EventArgs e)
{
    Method1();
    Method2();
}

void Method1()
{
    Task task = new(ThreadedMethod1);

    task.Start();
}

void Method2()
{
    Task task = new(ThreadedMethod1);

    task.Start();
}

void ThreadedMethod1()
{
    for (int i = 0; i < 50; i++)
    {
        TestClass1.Ints.Add(i);

        textBox1.AppendText(TestClass1.Ints[i].ToString() + "\r\n");
    }
}

void ThreadedMethod2()
{
    for (int i = 0; i < 50; i++)
    {
        TestClass1.Ints.Add(i);

        textBox1.AppendText(TestClass1.Ints[i].ToString() + "\r\n");
    }
}

위 예제에서 TestClass1은 서로 다른 뜨레드에서 실행되는 서로 다른 메떠드로 보내진다. 서로 다른 뜨레드에서 실행되는 하나의 메떠드에서는 lock으로 쉽게 대이터 래이스를 막을 수 있지만 위와 같은 경우에도 그럴까?

그렇다. 아래의 코드는 대이터 래이스를 일으키지 않는다.

void ThreadedMethod1()
{
    lock (TestClass1)
    {
        for (int i = 0; i < 50; i++)
        {
            TestClass1.Ints.Add(i);

            textBox1.AppendText(TestClass1.Ints[i].ToString() + "\r\n");
        }
    }
}

void ThreadedMethod2()
{
    lock (TestClass1)
    {
        for (int i = 0; i < 50; i++)
        {
            TestClass1.Ints.Add(i);

            textBox1.AppendText(TestClass1.Ints[i].ToString() + "\r\n");
        }
    }
}

lock은 어떤 뜨레드에서 어떤 메떠드가 실행되든 lock의 대상이 되는 객체는 완전하게 동기화한다. 즉 대이터 래이스를 막고 순서대로 처리한다.