티스토리 뷰

Android, Winform, Swing, JavaFX등 대부분의 GUI 프레임워크들은 GUI를 다룰때 단일 쓰레드만을 지원합니다. 물론 별도의 쓰레드를 통한 작업이 가능하지만, GUI를 다루기 위해서는 다시 GUI를 담당하는 EDT(Event Dispatch Thread)를 이용해야만 합니다. 그 이유는 무엇일까요?



멀티쓰레딩을 위한 시도

AWT는 일반적인 다중 쓰레드를 지원하는 java library 로 시작하였습니다. 하지만 AWT를 이용함으로써 발생하는 교착상태와 경쟁 조건(race condition)등의 문제점이 발견되면서 단일 쓰레딩으로 방향을 돌렸고 그 이후에 개발된 Swing에서는 멀티쓰레딩을 제한한 설계를 하게 되었습니다. AWT외에 다른 프레임워크들도 다중 쓰레드를 사용하고자 하는 시도는 많았지만 AWT와 같은 문제점들이 발생하였고, 결국은 대부분의 프레임워크들이 단일 쓰레드 모델에 정착하게 되었습니다.


EDT(Event Dispatch Thread)는 AWT에서 사용되는 단일 쓰레드 이벤트 큐 모델이 배경이 되었습니다. 이벤트 큐 모델은 프레임워크가 이벤트 처리용 GUI 쓰레드를 만들고, GUI 쓰레드는 큐에 쌓여 있는 이벤트를 꺼내 이벤트 처리 메소드를 호출해 기능을 동작시키는 방식입니다.



멀티 쓰레딩의 문제점

GUI 프레임워크에서 멀티 쓰레드를 사용하게 된다면 데드락에 빠지기 쉽습니다. 사용자가 시작한 동작은 OS에서 응용프로그램으로 버블 업 되는 구조 입니다. 예를들어 마우스 클릭이 OS에 의해서 감지되고 툴킷에 의해 "마우스 클릭" 이벤트로 바뀌며 최종적으로 응용프로그램의 상위 레벨로 전달됩니다. 반면에 응용 프로그램에서 시작된 작업은 OS로 버블 다운되어 전달됩니다.


이러한 구조를 통해 동일한 GUI 객체에 접근하도록 결합하게 되면 일관성 없는 lock(쓰레드 동기화)를 만들게 되어 쓰레드 교착상태가 발생하게 됩니다. 


다중 쓰레드GUI 프레임워크에서 교착상태를 일으키는 또 다른 원인은 MVC(Model-View-Control)패턴입니다. MVC 모델에서 컨트롤러는 모델 내부의 값을 불러다 사용하고, 변경된 내용은 뷰를 통해 화면에 표시합니다. 하지만 반대로 컨트롤러에서 뷰의 기능을 호출하면서 모델 내부의 상태를 확인하기 위해 모델의 기능을 호출하는 경우도 있습니다. 그러다 보니 객체마다 락이 걸리는 순서가 잘못 맞물려 데드락에 걸릴 가능성이 높습니다.


또한 멀티쓰레딩으로 부터 얻는 버그나 문제의 양은 무의미한 성능 향샹을 위한 것보다 훨씬 많다는 결론을 경험을 토대로 얻었기 때문에 대부분의 GUI 프레임워크에서는 단일 쓰레드를 사용하고 있습니다.



단일 쓰레드

단일 쓰레드를 통해 GUI를 컨트롤 하게 되면 교착상태와 경쟁상태등에 대한 문제로부터 벗어나게 됩니다. 하지만 단일 쓰레드에서는 긴 시간의 작업을 수행할때 GUI에 대한 작업을 수행할 수 없어 GUI가 멈춤 또는 응답없음상태가 발생하는 문제점과 여러개의 작업을 동시에 처리할 수 없는 비효율 적인 문제점들도 있었습니다. 따라서 이를 해결하기 위해 개발된 방법이 바로 이벤트 큐 구조입니다.


별도의 쓰레드를 통해 긴 시간의 작업을 수행하고 변경해야하는 GUI이벤트를 이벤트 큐에 삽입해 GUI 전담 쓰레드가 작업하게 됩니다. 이를 통해 GUI 작업에 대해서만 단일 쓰레드를 제한하고 별도의 작업은 멀티쓰레드를 사용할 수 있는 현재의 구조가 나오게 되었습니다.



쓰레드를 더 쉽게 사용하는 방법

이러한 구조로 인해 GUI 프로그램을 개발할때 멀티 쓰레드를 사용하는것이 불가피해졌습니다. 하지만 쓰레드를 다루는 것은 쉽지 않고 세심하고 정확하게 다루지 않으면 여러 문제를 발생시킬 수 있습니다. 때문에 Java, C# 등에서는 GUI를 개발할 때 쓰레드 복잡성을 숨겨주고 좀 더 쉽게 사용할 수 있도록 class를 제공하고 있습니다.


  Java - SwingWorker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  SwingWorker<Boolean, Integer> worker = new SwingWorker<Boolean, Integer>() {
 
   @Override //작업을 수행하는 메소드
   protected Boolean doInBackground() throws Exception {
    //작업할 내용
    for (int i = 0; i <= 10; i++) {
     Thread.sleep(1000);
     publish(i);
    }
 
    return true;
   }
 
   //작업이 종료되었을시(doInBackground 메소드가 종료되었을 시) 호출되는 메소드
   protected void done() {
    boolean status;
    try {
     status = get();
     statusLabel.setText("Completed with status: " + status);
    } catch (InterruptedException | ExecutionException  e) {
    }
   }
 
   @Override //작업 수행중에 업데이트가 필요할 시(publish 가 호출되었을시) 호출되는 메소드
   protected void process(List<Integer> chunks) {
    int mostRecentValue = chunks.get(chunks.size()-1);
    countLabel1.setText(Integer.toString(mostRecentValue));
   }
   
  };
  
  worker.execute(); //SwingWorker 작업 실행
cs


  C# - BackgroundWorker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
         private BackgroundWorker bw = new BackgroundWorker(); //BackgroundWorker클래스를 선언 및 할당
        public void executeWorker()
        {
            bw.WorkerReportsProgress = true;
            //스레드 작업 도중 취소 가능 여부
            bw.WorkerSupportsCancellation = true;
            //스레드가 run시에 호출되는 핸들러 등록
            bw.DoWork += new DoWorkEventHandler(bw_DoWork); 
            // ReportProgress메소드 호출시 호출되는 핸들러 등록
            bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); 
            // 스레드 완료(종료)시 호출되는 핸들러 동록
            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
 
            // 스레드가 Busy(수행중)가 아니라면
            if (bw.IsBusy != true)
            {
                // 스레드 작동!! 아래 함수 호출 시 위에서 bw.DoWork += new DoWorkEventHandler(bw_DoWork); 에 등록한 핸들러가 호출됨
                bw.RunWorkerAsync();
            }
 
 
            // 해당 스레드가 수행도중 취소를 지원한다면
            if (bw.WorkerSupportsCancellation == true)
            {
                //스레드 취소(종료), 이 메소드를 호출하면 CancellationPending 속성이 true로 변경됨
                bw.CancelAsync();
            }
        }
 
        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
 
            for (int i = 1; (i <= 10); i++)
            {
                //CancellationPending 속성이 true로 set되었다면(CancelAsync() 메소드가 호출되었다면)
                if ((worker.CancellationPending == true))
                {
                    //작업 중단
                    e.Cancel = true;
                    break;
                }
                else
                {
                    // 스레드에서 수행할 작업 작성
                    System.Threading.Thread.Sleep(500);
                   // 스레드 진행상태 알림 
                   // bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); 등록한 핸들러가 호출 됩니다.
                    worker.ReportProgress((i * 10));
                }
            }
        }
 
        private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //작업 진행률 표시
            this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
        }
 
        //스레드의 run함수가 종료될 경우 해당 핸들러가 호출됩니다.
        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
           //작업 종료시 수행할 작업
        }
cs


  Android - AsyncTask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }
 
        @Override //작업 수행 메소드
        protected Long doInBackground(URL... urls) {
            int count = urls.length;
            long totalSize = 0;
            for (int i = 0; i < count; i++) {
                // 내려받는 파일 size 합산 작업
                // totalSize += Downloader.downloadFile(urls[i]);
 
                publishProgress((int) ((i / (float) count) * 100));
                // Escape early if cancel() is called
                if (isCancelled()) break;
            }
            return totalSize;
        }
 
        @Override //작업 진행률 메소드(publishProgress 메소드 호출시)
        protected void onProgressUpdate(Integer... progress) {
            // 파일 다운로드 퍼센티지 표시 작업
            // setProgressPercent(progress[0]);
        }
 
        @Override //작업 완료후 호출되는 메
        protected void onPostExecute(Long result) {
            // 다 받아진 후 받은 파일 총용량 표시 작업
            // showDialog("Downloaded " + result + " bytes");
        }
 
        @Override
        protected void onCancelled(Long aLong) {
            super.onCancelled(aLong);
        }
 
        @Override
        protected void onCancelled() {
            super.onCancelled();
        }
    }
 
new DownloadFilesTask().execute(new URL("파일 다운로드 경로1"), new URL("파일 다운로드 경로2"));
//출처 : http://corej21.tistory.com/38
cs


위에서 소개해 드린 Class들은 모두 해당 GUI프레임워크에서 별도의 핸들링이나 작업 없이 Background작업과 GUI 컨트롤을 모두 수행할 수 있습니다. 대략적으로 어떻게 사용되는지만 보여드리는 예제들이니 자세한 사용법은 공부를 하셔야 합니다.

댓글
댓글쓰기 폼
Total
354,596
Today
15
Yesterday
658
링크
«   2019/11   »
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함