Thread 관련 질문 드립니다.

안녕하세요 . 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. “이게 ctrl+f5로 실행 할때 UI가 먹통이 되는 현상이 있고”
  2. “디버그 모드로 breakpoint없이 실행 하면 스레드가 정상적으로 Join 됩니다.”
  3. “그리고 button2_Click 메서드에 breakpoint를 찍으면 thread가 Join이 되지않고 프로그램이 먹통이되는 그런 현상이 있습니다.”

안녕하세요
1번: Thread.Sleep(1000); 라인을 주석아웃 시키셨는데요. 엄청 빠르게 계속 UI스레드에 label1.Text를 갱신시키는 명령을 내리신게 됩니다. UI스레드가 미친듯이 바쁘기 때문에 먹통으로 보입니다.
2번: 답변은 못드리고 질문이 있네요. 스레드가 정상적으로 조인 된다고 하셨는데, 그렇다면 먹통이 되지 않고 버튼2를 누르셨다는 건가요?
3번: 마찬가지로 답변은 못드리고, 질문도 비슷한데, 먹통이 안된채로 버튼2를 누르는데 성공하신건가요? 그런 다음에 중단점이 정상적으로 걸렸으나 time.Join(); 줄에서 멈춘다는건가요?

1개의 좋아요

먹통이 되지 않고 UI 정상적으로 껏습니다.

1개의 좋아요

2번 : 먹통이 되지 않고 button2 눌렀고 UI까지 정상적으로 껏습니다.
3번 : 버튼2 눌렀고 time.Join();에서 f10으로 breakpoint 이동시키면 UI창 뜨고 딱 먹통이됩니다.

1개의 좋아요

2번: 신가하네요? UI스레드가 풀파워로 라벨 갱신하느라 먹통일건데 버튼2가 눌러지는건 또 무엇인지…
디버깅 모드는 자동으로 제한을 두는걸까요? 스레드 슬립을 주석해제하신건 아니신지요?

3번: 지금 상태가 이해는 안되지만, 중단점이 조인에서 멈추는 건 대상 스레드가 안끝났기 때문에 시킨대로 기다리고 있는 것일 수 있습니다.

전체 코드가 저 상태가 맞나요? 저도 하나 만들어서 해봐야겠네요.

1개의 좋아요

릴리즈 / 디버그 모두 상관 없이 UI가 멈추는게 맞습니다.
아마 디버그때만 정상적으로 join() 되었다는 것은 우연한 타이밍이 아닐까 합니다.



Invoke() 를 사용하면 SynchronizationContext를 통해 Get() 으로 호출되서

UI스레드에게 작업을 위임하고 대기중 입니다.

이때

Join() 으로 해당 스레드가 종료 될때 까지 대기 중인 상태로 넘어갑니다.

UI 스레드에서 작업을 처리하려면 현재 스레드가 Join() 대기 상태에 있기 때문에 데드락이 발생합니다.

3개의 좋아요

아 Invoke가 비동기인줄 알았는데 동기인건가요? 이쪽에서 대기타고 저쪽에서 대기타고;; 그렇게 됐군요 감사합니다!

1개의 좋아요

2번: 주석 해제안했습니다…
3번: 이번엔 time.Join 위에 MessageBox를 하나 했더니 이번엔 UI먹통이 안되네요 …

1개의 좋아요

그러니까 time.Join으로 인해서 남은 라벨이 시간을 표시하는 작업을 Invoke로 UI스레드에서 수행해야되는데 Join으로 인해서 UI스레드 호출이 차단되서 UI가 먹통이 되는 현상이 생긴건가요 ??

맞다면 이때 먹통안되고 thread를 잘 종료하려면 어떻게 하는게 좋을까요 ?

그리고 마지막으로 time.Join(); 위에 MessageBox를 하나 띄웠는데 UI먹통 되는 현상이 없어졌는데
왜 그런지 궁금합니다.

1개의 좋아요

제가 저 코드대로 만들어서 해보니까 잘못된 정보를 드렸네요!

1번: 이게 잘못됐습니다. 저는 글만 봤을 때 버튼1 누른 뒤 먹통이 된거라고 쓴이분이 쓰신줄 알았어요.
근데 버튼1 눌렀을 땐 먹통이 안되네요. UI스레드가 미친듯이 화면 텍스트 갱신할 것이라고 착각했으나 실제로 만들어보니 버튼1만 눌렀을 땐 먹통이 되지 않았습니다. 아마도 aroooong 님이 알려주신대로 동기형 실행이니까 Invoke()가 미친듯이 연속으로 날라가지 않고 하나씩 호출되서 그럴 것 같습니다.

2번: 위에 상태라면 주석해제 하지 않아도 말씀하신바대로 입니다!

3번: 이거는 타이밍 이슈 같습니다. 메시지박스를 추가로 두어서 타이밍이 달라 진 것이죠.
aroooong 님이 말한 타이밍이 재현되면 데드락이 발생합니다. 제가 1번을 오해한게 쓴이분이 ctrl+f5로 먹통일 때라는게 버튼1 클릭인줄 알았으나 버튼2 클릭이지요?

2개의 좋아요

버튼 2클릭일때가 맞습니다. 메시지 박스를 두었을때 타이밍이 달라졌다라고 하시는데 잘 이해가 안되네요 ㅜ

1개의 좋아요

어음 뭔가 도표 같이 그려드려야 이해되실 거 같은데, 그거는 제가 그려볼게요.
일단 새로해 주신 질문 “맞다면 이때 먹통안되고 thread를 잘 종료하려면 어떻게 하는게 좋을까요 ?”
이거부터 답변드리면 time.Join() 을 안하시고 time 스레드가 하는 일은 Timecheck() 뿐이기 때문에 isStop = false; 만 하시면 다음번 while 턴에서 while 이 종료되고, while 이후에는 Timecheck()에 아무것도 없기 때문에 time 스레드는 끝납니다. 다른거 하실거 없이요.

1개의 좋아요

풀어서 설명하면 이렇게 됩니다.

  1. UI 스레드(메인 스레드)가 새로운 time이라는 작업 스레드를 생성합니다.
  2. time 스레드는 Invoke로 동기적으로 UI 스레드에게 작업을 위임 합니다.

여기 까지는 의도하는 정상적인 동작입니다.


버튼2를 눌렀을때

  1. time 스레드를 Join()으로 해당 스레드가 종료 될때 까지 대기 합니다. (메세지 펌프도 멈춤)
  2. time 스레드는 대기 중인 응답이 없는 UI 스레드에게 Invoke 처리를 하고 마냥 기다립니다.

자 그런데 Join() 전에 메세지 박스가 띄워집니다.
메세지 박스는 새로운 모달 다이얼로그 창입니다.
모달 다이얼로그 창이 표시 되면 새로 메세지 Dispatch 하는 메세지 루프가 생성되는데
메세지 박스 모달 다이얼로그에서 메세지 Dispatch가 정상적으로 처기 되었기 때문에
time스레드는 정상 종료 되는 것입니다.


time스레드를 정상 종료 하시고 싶은거라면 그냥 자연스럽게 isStop 플래그만 변경해서 해당 로직을 끝내시면 됩니다.

5개의 좋아요

도표까지 그려주신다니 감사합니다 !! 덕분에 도움이 되었습니다. 감사합니다.

1개의 좋아요

아아 감사합니다! 메시지루프 말고 단순 타이밍이슈인줄 알았는데 그게 아니네요!
메시지박스 없는 코드에서 데드락 될때있고 안될때 있고 한줄 알고
[데드락 될때의 호출 순서, 안될때의 호출 순서 ] 에 대한 도표 그릴 뻔 했어요! ㅋㅋㅋ
무조건 데드락이 발생되는 것이었겠네요.

1개의 좋아요

isStop 플래그로 어차피 time 스레드는 종료가되서 WaitSleepJoin상태가 되어버리네요.
이러면 굳이 Join을 쓸필요가 없는 거군요. 질문 답변해주셔서 감사드립니다!!

2개의 좋아요

엇, 도표 안그릴거에요. 잘못된 생각이었거든요. 죄송해요 ㅋ
애로우 님이랑 아룽 님 덕분에 새로 배워가요 ㅋ
아룽 님 설명이 이해 안되시는 부분이 있으실까요? 그거라도 부연설명을 드려야 할까봐요 헛소리할 뻔만 죄송스러움에;;;

1개의 좋아요

네. 굳이 Join은 필요 없었습니다.

1개의 좋아요

ㅋㅋㅋ메시지 Dispatch 이부분이 생소하네요 알려주시면 감사하겠습니다.

1개의 좋아요

아 근데 또;; 제가 워낙 얼기설기 알고 있는 사람인데, 그래서 거짓정보도 드릴뻔했는데, 그런 제가 부연설명을 하는게 올바라 보이지는 않지만;; 새겨 들으신다면 ㅋㅋ

이게 ui 프로그램들이 거진 ui 처리를 위해서 큐라고 할까요 루프가 있거든요
mfc 인가 윈도우쪽에서 “message” 라고 명명을 하는것 같아요.
윈폼도 mfc 그이후 윈도우 툴이니까 이름이 “메시지 루프” 인 것 같아요.
“dispatch” 라는 단어는 업무를 처리한다는 의의? 기능을 부르는데 있어서
업무 이름이 메시지니까 처리라는 뜻의 단어 중 디스패치를 명명한 것 같네요?
아무튼 그냥 ui 작업이 순서대로 쌓여있는 애가 있고 순서대로 처리(디스패치) 한다 랄까요.

UI스레드가 메시지루프를 처리하는 애인데
아룽님이 알려주신바 join() 에 의해서 메시지 처리도 멈춰버려서 (정상적인 경우라면 join의 타겟 스레드가 끝난 후에 다시 메시지 처리도 시작됐겠죠) 이 앱이 먹통이 된건데.
아룽님의 설명에 의하면 새로 추가하신 메시지박스라는게 다른 폼? 별게의 ui 그릇? 창? 이라서 거기에 메시지루프가 독립적으로 신설되고, 그래서 label1 문구 갱신이 처리가 되면 작업자스레드에서 invoke()도 끝이나고 그 후에 while도 false라서 작업자스레드 자체가 끝나버리고 그러면 ui스레드의 join()도 회수?되고 다 끝장

1개의 좋아요