Handler.sendMessage delay 시간이 정확한지 체크 및 스레드 종료 타이밍 테스트 입니다.
어떤 방법으로 했을때가 가장 빠르게 종료 될지 의문이네요..
밑에서 결과를 보시면 알겠지만 종료이후 일을 조금 더 하고 끝나는군요..
게다가 마지막에 t2 에서 핸들러에게 날린것은 타이밍상 맞지 않은데 마지막으로
버퍼 플러쉬 해주듯이 호출 합니다. 이부분도 의문이네요..--;;
혹시 비슷한 문제를 해결하신분 있으신가요? 공유해봅시다~!!
package lowmans.test; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.util.Log; public class TestActivity extends Activity { private boolean sendEnable = true; private Thread1 t1; private Thread2 t2; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); t1 = new Thread1(); t1.start(); t2 = new Thread2(); t2.start(); } class Thread1 extends Thread { private boolean loopEnable = true; private int cnt = 0; public void run() { while (loopEnable) { try { sleep(100); } catch (InterruptedException e) { } if (++cnt > 10) { cnt = 0; } Log.i("#@#", "----- Loop : " + cnt + "------"); } }; public void setCnt(){ cnt = 0; } public void release() { loopEnable = false; } }; class Thread2 extends Thread { private boolean loopEnable = true; public void run() { while (loopEnable) { if (sendEnable) { h.sendEmptyMessageDelayed(0, 1000); sendEnable = false; } } }; public void release() { loopEnable = false; } }; Handler h = new Handler() { public void handleMessage(android.os.Message msg) { sendEnable = true; t1.setCnt(); Log.i("#@#", "handler ###"); }; }; public void onBackPressed() { Log.i("#@#", "onBackPressed()"); t1.release(); t2.release(); finish(); }; }
-------- 결과 --------
05-24 17:24:05.839: I/#@#(8202): ----- Loop : 1------
05-24 17:24:05.941: I/#@#(8202): ----- Loop : 2------
05-24 17:24:06.039: I/#@#(8202): ----- Loop : 3------
05-24 17:24:06.140: I/#@#(8202): ----- Loop : 4------
05-24 17:24:06.238: I/#@#(8202): ----- Loop : 5------
05-24 17:24:06.339: I/#@#(8202): ----- Loop : 6------
05-24 17:24:06.441: I/#@#(8202): ----- Loop : 7------
05-24 17:24:06.539: I/#@#(8202): ----- Loop : 8------
05-24 17:24:06.640: I/#@#(8202): ----- Loop : 9------
05-24 17:24:06.742: I/#@#(8202): ----- Loop : 10------
05-24 17:24:06.746: I/#@#(8202): handler ###
05-24 17:24:06.839: I/#@#(8202): ----- Loop : 1------
05-24 17:24:06.941: I/#@#(8202): ----- Loop : 2------
05-24 17:24:07.039: I/#@#(8202): ----- Loop : 3------
05-24 17:24:07.140: I/#@#(8202): ----- Loop : 4------
05-24 17:24:07.242: I/#@#(8202): ----- Loop : 5------
05-24 17:24:07.339: I/#@#(8202): ----- Loop : 6------
05-24 17:24:07.441: I/#@#(8202): ----- Loop : 7------
05-24 17:24:07.542: I/#@#(8202): ----- Loop : 8------
05-24 17:24:07.640: I/#@#(8202): ----- Loop : 9------
05-24 17:24:07.742: I/#@#(8202): ----- Loop : 10------
05-24 17:24:07.746: I/#@#(8202): handler ###
05-24 17:24:07.839: I/#@#(8202): ----- Loop : 1------
05-24 17:24:07.941: I/#@#(8202): ----- Loop : 2------
05-24 17:24:08.042: I/#@#(8202): ----- Loop : 3------
05-24 17:24:08.140: I/#@#(8202): ----- Loop : 4------
05-24 17:24:08.242: I/#@#(8202): ----- Loop : 5------
05-24 17:24:08.343: I/#@#(8202): ----- Loop : 6------
05-24 17:24:08.441: I/#@#(8202): ----- Loop : 7------
05-24 17:24:08.542: I/#@#(8202): ----- Loop : 8------
05-24 17:24:08.640: I/#@#(8202): ----- Loop : 9------
05-24 17:24:08.742: I/#@#(8202): ----- Loop : 10------
05-24 17:24:08.746: I/#@#(8202): handler ###
05-24 17:24:08.843: I/#@#(8202): ----- Loop : 1------
05-24 17:24:08.941: I/#@#(8202): ----- Loop : 2------
05-24 17:24:08.996: I/#@#(8202): onBackPressed()
05-24 17:24:09.042: I/#@#(8202): ----- Loop : 3------
05-24 17:24:09.750: I/#@#(8202): handler ###
마지막 handler ###의경우 sendEmptyMessageDelayed에 의해 1초뒤에 실행되도록 되어 있기때문에 스레드가 죽더라도 스레드와 무관하게 출력되게 됩니다.
다음과 같이 소스 수정을 하시면 됩니다.
public void release() {
loopEnable = false;
//핸들러에 남아있는 메세지 삭제
h.removeMessages(0);
}
public void onBackPressed() {
Log.i("#@#", "Start onBackPressed()");
try {
t1.release();
t2.release();
//t1이 종료될때까지 대기
t1.join();
//t2이 종료될때까지 대기
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finish();
Log.i("#@#", "End onBackPressed()");
};
일단 onBackPressed() 로그가 찍힌 후에 Loop 3이 찍힌 것은 t1.release()가 호출되기 전에 이미 Thread1에서 while문 안의 어느 부분을 수행하는 시점이라 생각이 듭니다. sleep상태든 cnt을 증가시키는 상태이든 상관없이 이미 while문 안의 조건에 들어왔기 때문에 로그가 찍히는 것 같구요.
handler가 출력된 것도 거의 비슷한 이유인데, release되기 전에 이미 Handler에게 "1초뒤에 로그를 찍어"라고 등록을 했기 때문에 Thread2의 생명과는 무관하게 로그를 출력한 것이죠.
쓰레드는 일반적으로 interrupt()메서드로 죽이는 것으로 알고 있는데, 이 메서드를 사용해도 같은 결과일거라 예상이 됩니다.
그래서 isCanceled라는 boolean 플래그를 두고, 로그 출력 작업전에 이 플래그가 셋팅되어져 있다면 로그를 출력하지 않는 식으로 하면 될 것 같네요.
그런데 이런 방식의 문제점이 있는데요..
아즈라엘님이 예를 든 상황처럼 while문의 "마지막에서만" 다른 클래스에 영향을 미칠 수 있는 작업을 하는 경우에는 문제가 없습니다.
예를들어 쓰레드의 run메서드에서 아래와 같은 작업을 한다고 치죠.
while(true) {
//thread의 내부 변수만을 조작하는 작업1.
//thread의 내부 변수만을 조작하는 작업2.
//thread의 내부 변수만을 조작하는 작업3.
//thread 외부에 결과를 알려줌(예를들면 UI에 반영한다든지, 다른 클래스의 데이터를 변경한다든지)
}
이렇게 while문의 마지막에서만 다른 클래스에게 영향을 미친다면 그 부분만을 플래그 체크로 막을 수 있습니다.
if(!isCanceled) {
//thread 외부에 결과를 알려줌(예를들면 UI에 반영한다든지, 다른 클래스의 데이터를 변경한다든지)
}
그런데 쓰레드의 중간 중간에 다른 클래스의 데이터를 변경한다하면 이건 좀 문제가 될 수 있을 것 같네요.
특히 쓰레드 중간 중간에 발생하는 데이터 변경에 atmoic성이 보장되어야 한다면,, 즉 모든 값이 변경되거나 그렇지 않은 경우에는 하나도 변경되지 않아야하는 상황이라면 약간 문제가 생길 수 있습니다.
물론 이 문제도 외부 객체의 데이터 변경은 쓰레드의 마지막 부분으로 옮겨서 하나의 if블럭 안에서 처리하면 될 것 같습니다.
(이런 상황이 있을지는 모르겠지만, 만약 다른 객체들의 데이터 변경을 쓰레드의 마지막 부분으로 옮기지 못하고 쓰레드의 중간중간에 산발적으로 발생해야만한다면 상황이 어려워지겠죠.)
이미 생기고 있습니다.
스레드에 진입시에 비교를 하는것들 중 다른쪽 스레드에서 데이터를 바꾸었고 그러 인해 콜백으로 분기한 함수가
제대로 움직이지 못하는 사례가 발생되고 있습니다.
그래서 메서드카운터를 만들어서 호출쪽에서 증가 시키고 콜백쪽에서 감소시켜서 카운트를 제어 하여 거의 싱크를 맞추어
놨는데..이렇게 해도 아주 희박하게 문제가 발생되는군요..^^
스레드를 멈추고자 하는건 화면에서 사라질때 잔상이나 잔여 작업을 좀 빠르게 끝내고 싶어서 입니다.
그런데 .. 전환 과정에서 매끄럽게 이어지지 못하는게 아쉽기만 하네요.
긴 답변 고맙습니다. 소중하게 생각하여 적용하도록 해보겠습니다 ^^
마지막 핸들러는 확실히 문제가 있군요..ㅎㅎ
제가 remove를 안했군요..ㅠㅠ
지금 하고 있는 프로젝트 중간에 폼을 그대로 가져와서 유닛테스트 비슷하게 하는거라서..
중간에 누락된게 있었군요..
마음이 급하니 아는것도 보이지 않고.. 혼자 끙끙대고 있으니 외롭네요 ㅎㅎ
혹시 본인이 사용하여 best 하게 interrup 를 한 사례는 없나요?
이럴땐 자랑하셔도 됩니다.
이미 while안에 들어간 경우 끝나기 전에 종료 시키는 방법은 없을것 같네요.
매라인 마다 체크하면 되긴 하겠지만 비효율 적이고 onBackPressed에서 finish호출하기전에 각 예외 처리 해주는 방법밖에는...
실제로 finish부르기 전까지는 화면 전환이 이루어 지지 않으니 문제 없을것 같습니다.
참고로 interrupt를 이용한 스레드 종료를 사용하려면 block된 상태여야 합니다.(ex. wait or sleep상태)
아래와 같이 while문 밖에 try catch문이 걸려있고 sleep나 wait으로 bolck된 상태에서 interrupt를 호출하면 catch에 걸리게 됩니다.
public void run() {
try {
while (loopEnable) {
if (++cnt > 10) {
cnt = 0;
}
Log.i("#@#", "----- Loop : " + cnt + "------");
Thread.sleep(100);
}
} catch (Exception e) {
Log.i("#@#", "Exception1 ###");
}
};
가장 빠른지는 모르겠지만
run() 내에서 while을 체크할 때 스레드의 !isInterrupted()로 체크하고,
(!주의, interrupt를 당해야 true이므로 정상작동은 false일 때 입니다.)
스레드를 끝내고 싶을 때 해당 스레드를 interrupt하는 방식이 가장 좋다고 알고 있습니다.