[c#] Contol.Invoke와 Control.BeginInvoke의 비교

Invoke와 BeginInvoke는 멀티 뜨레드를 다룰 때 자주 쓴다.

Invoke는 Control로 제어권이 넘어간 뒤 돌아올 때를 기다리고 BeginInvoke는 기다리지 않는다. 조금 더 어렵게 설명하면 전자는 synchronous 즉 동기적이고 후자는 asynchronous 즉 비동기적이다.

아래 예제에서 BeginInvoke를 한 거는 label 출력 명령을 떠나 보낸 뒤 바로 제 갈 길을 간다. 따라서 BeginInvoke가 실행된 뒤 그 끝을 기다리지 않고 바로 MessageBox.Show가 실행된다.

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

void Method1()
{
    label1.Invoke(invoke);

    void invoke()
    {
        label1.Text = "1";

        Thread.Sleep(2000);
    }

    MessageBox.Show("waited");
}
        
private void button2_Click(object sender, EventArgs e)
{
    Task.Run(Method2);
}

void Method2()
{
    label1.BeginInvoke(invoke);

    void invoke()
    {
        label1.Text = "2";

        Thread.Sleep(2000);
    }

    MessageBox.Show("immediate");
}

아래와 같은 경우 문제가 된다.

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

void Method1()
{
    for (int i = 0; i < 5; i++)
    {
        button1.Invoke(method);

        void method()
        {
            button1.Text = i.ToString(); // 4
        }
    }
}

private void button2_Click(object sender, EventArgs e)
{
    Task.Run(() => Method2());
}

void Method2()
{
    for (int i = 0; i < 5; i++)
    {
        button2.BeginInvoke(method);

        void method()
        {
            button2.Text = i.ToString(); // 5
        }
    }
}

보통의 경우라면 위의 for 구문에서 i는 4로 끝날 것으로 의도한다. 하지만 BeginInvoke를 쓰면 5로 끝난다. BeginInvoke가 실행되는 동안 다른 뜨레드에서 for가 i++를 실행하기 때문이다. 이런 문제를 피하려면 BeginInvoke 아닌 Invoke를 쓰는 게 좋다.