안녕하세요 . 3개월차 신입개발자인데 c# 공부하다가 thread 관련 문제가 생겨서 질문드립니다.
우선 윈폼코드입니다.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace threadjoin
{
public partial class Form1 : Form
{
private bool isStop = false;
Thread time;
public Form1()
{
InitializeComponent();
time = new Thread(Timecheck);
}
private void Timecheck()
{
isStop = true;
while (isStop)
{
//Thread.Sleep(1000);
if (label1.InvokeRequired)
{
Invoke(new MethodInvoker(() => { label1.Text = DateTime.Now.ToString(); }));
Console.WriteLine($"Current time : {DateTime.Now.Millisecond}");
}
}
}
private void button1_Click(object sender, EventArgs e)
{
isStop = true;
time.Start();
}
private void button2_Click(object sender, EventArgs e)
{
isStop = false;
time.Join();
}
}
}
그냥 간단하게 라벨에 현재 시각을 표시하는 스레드를 버튼 눌러서 스타트 시키고 Join 하는 기능이있는데 이게 ctrl+f5로 실행 할때 UI가 먹통이 되는 현상이 있고 디버그 모드로 breakpoint없이 실행 하면 스레드가 정상적으로 Join 됩니다. 그리고 button2_Click 메서드에 breakpoint를 찍으면 thread가 Join이 되지않고 프로그램이 먹통이되는 그런 현상이 있습니다. 이유좀 알려주시면 감사하겠습니다. 참고로 visual studio 2015 사용했습니다.
1번: 이게 잘못됐습니다. 저는 글만 봤을 때 버튼1 누른 뒤 먹통이 된거라고 쓴이분이 쓰신줄 알았어요.
근데 버튼1 눌렀을 땐 먹통이 안되네요. UI스레드가 미친듯이 화면 텍스트 갱신할 것이라고 착각했으나 실제로 만들어보니 버튼1만 눌렀을 땐 먹통이 되지 않았습니다. 아마도 aroooong 님이 알려주신대로 동기형 실행이니까 Invoke()가 미친듯이 연속으로 날라가지 않고 하나씩 호출되서 그럴 것 같습니다.
2번: 위에 상태라면 주석해제 하지 않아도 말씀하신바대로 입니다!
3번: 이거는 타이밍 이슈 같습니다. 메시지박스를 추가로 두어서 타이밍이 달라 진 것이죠.
aroooong 님이 말한 타이밍이 재현되면 데드락이 발생합니다. 제가 1번을 오해한게 쓴이분이 ctrl+f5로 먹통일 때라는게 버튼1 클릭인줄 알았으나 버튼2 클릭이지요?
어음 뭔가 도표 같이 그려드려야 이해되실 거 같은데, 그거는 제가 그려볼게요.
일단 새로해 주신 질문 “맞다면 이때 먹통안되고 thread를 잘 종료하려면 어떻게 하는게 좋을까요 ?”
이거부터 답변드리면 time.Join() 을 안하시고 time 스레드가 하는 일은 Timecheck() 뿐이기 때문에 isStop = false; 만 하시면 다음번 while 턴에서 while 이 종료되고, while 이후에는 Timecheck()에 아무것도 없기 때문에 time 스레드는 끝납니다. 다른거 하실거 없이요.
time 스레드를 Join()으로 해당 스레드가 종료 될때 까지 대기 합니다. (메세지 펌프도 멈춤)
time 스레드는 대기 중인 응답이 없는 UI 스레드에게 Invoke 처리를 하고 마냥 기다립니다.
자 그런데 Join() 전에 메세지 박스가 띄워집니다.
메세지 박스는 새로운 모달 다이얼로그 창입니다.
모달 다이얼로그 창이 표시 되면 새로 메세지 Dispatch 하는 메세지 루프가 생성되는데
메세지 박스 모달 다이얼로그에서 메세지 Dispatch가 정상적으로 처기 되었기 때문에
time스레드는 정상 종료 되는 것입니다.
time스레드를 정상 종료 하시고 싶은거라면 그냥 자연스럽게 isStop 플래그만 변경해서 해당 로직을 끝내시면 됩니다.
아아 감사합니다! 메시지루프 말고 단순 타이밍이슈인줄 알았는데 그게 아니네요!
메시지박스 없는 코드에서 데드락 될때있고 안될때 있고 한줄 알고
[데드락 될때의 호출 순서, 안될때의 호출 순서 ] 에 대한 도표 그릴 뻔 했어요! ㅋㅋㅋ
무조건 데드락이 발생되는 것이었겠네요.
아 근데 또;; 제가 워낙 얼기설기 알고 있는 사람인데, 그래서 거짓정보도 드릴뻔했는데, 그런 제가 부연설명을 하는게 올바라 보이지는 않지만;; 새겨 들으신다면 ㅋㅋ
이게 ui 프로그램들이 거진 ui 처리를 위해서 큐라고 할까요 루프가 있거든요
mfc 인가 윈도우쪽에서 “message” 라고 명명을 하는것 같아요.
윈폼도 mfc 그이후 윈도우 툴이니까 이름이 “메시지 루프” 인 것 같아요.
“dispatch” 라는 단어는 업무를 처리한다는 의의? 기능을 부르는데 있어서
업무 이름이 메시지니까 처리라는 뜻의 단어 중 디스패치를 명명한 것 같네요?
아무튼 그냥 ui 작업이 순서대로 쌓여있는 애가 있고 순서대로 처리(디스패치) 한다 랄까요.
UI스레드가 메시지루프를 처리하는 애인데
아룽님이 알려주신바 join() 에 의해서 메시지 처리도 멈춰버려서 (정상적인 경우라면 join의 타겟 스레드가 끝난 후에 다시 메시지 처리도 시작됐겠죠) 이 앱이 먹통이 된건데.
아룽님의 설명에 의하면 새로 추가하신 메시지박스라는게 다른 폼? 별게의 ui 그릇? 창? 이라서 거기에 메시지루프가 독립적으로 신설되고, 그래서 label1 문구 갱신이 처리가 되면 작업자스레드에서 invoke()도 끝이나고 그 후에 while도 false라서 작업자스레드 자체가 끝나버리고 그러면 ui스레드의 join()도 회수?되고 다 끝장