[c#] 멀티 뜨레드에서 lock과 await 같이 쓰기

아래의 코드에서 TestMethod1는 멀티 뜨레드에서 작동한다. 이 메떠드는 다른 뜨레드에서 실행되는 메떠드와 동기화되어야 한다. 그러면서 await도 해야 한다.

await를 lock 블록 안에서 쓰면 오류로 처리된다. 만약 await가 긴 시간을 쓴다면 lock이 걸린 다른 뜨레드는 그 시간을 기다려야 하기 때문이다. 이러면 멀티 뜨레드를 쓰는 의미가 줄어든다.

아래의 코드처럼 lock 블록 안에서 네스티드 메떠드를 만들어 await를 쓰면 오류로 처리되지는 않는다. 하지만 await부터 lock은 풀린다. 따라서 TestClass1.Int = 1과 TestClass1.Int = 2는 동기화되지 않아 race가 일어날 수 있다.

class TestClass
{
    public int Int;
}

TestClass TestClass1 = new();
object LockObject1 = new();

private void button1_Click(object sender, EventArgs e)
{
    Task.Run(TestMethod1);
}

void TestMethod1()
{
    lock (LockObject1)
    {
        // ...

        Task.Run(method);

        async Task method()
        {
            await Task.Delay(5000);

            TestClass1.Int = 1;
        }
    }
}

private void button2_Click(object sender, EventArgs e)
{
    lock (LockObject1)
    {
        // ...

        TestClass1.Int = 2;
    }

    Text = TestClass1.Int.ToString();
}

await는 이것대로 작동하게 하고 그 뒤부터 다시 동기화를 하려면 아래와 같이 다른 lock 오브젝트를 이용하여 다시 록을 걸어야 한다.

object LockObject1 = new();
object LockObject2 = new();

private void button2_Click(object sender, EventArgs e)
{
    lock (LockObject1)
    {
        // ...

        lock (LockObject2)
        {
            TestClass1.Int = 2;
        }
    }

    Text = TestClass1.Int.ToString();
}

void TestMethod1()
{
    lock (LockObject1)
    {
        // ...

        Task.Run(method);

        async Task method()
        {
            await Task.Delay(5000);

            lock (LockObject2)
            {
                TestClass1.Int = 1;
            }
        }
    }
}