Search

'ecore_timer'에 해당되는 글 1건

  1. 2011.10.24 [EFL] ecore_timer 사용 시 흔히 하기 쉬운 실수 및 올바른 사용법 1
[ EFL 게시물 목차 : http://yellowbirds.tistory.com/1 ]
안녕하세요? 천재태지 서주영입니다.

ecore_timer 는 EFL 에서 사용하는 타이머입니다.
타이머에 원하는 주기와 콜백을 설정해놓으면 매 주기마다 콜백이 불리게 됩니다. 타이머는 어플리케이션 제작 시에 흔히 사용하기 때문에 EFL 에서도 타이머를 제공합니다.

timer 를 생성하는 함수는 아래와 같습니다.
EAPI Ecore_Timer *                 
ecore_timer_add(double        in,  
                              Ecore_Task_Cb func,
                              const void   *data)
첫번째 인자로 double 형으로 주기를 입력 받습니다. 두번째 인자로 주기마다 불릴 콜백 함수를 입력받고, 마지막에는 콜백함수에 넘겨줄 사용자 데이터를 입력받습니다.

[유의사항 1 - 타이머의 주기]
기본적으로는 정해놓은 주기마다 콜백이 불리지만, 한가지 유의해야할 점은, 정확하게 해당 주기 마다 타이머가 불리는 것이 아니라
해당 주기의 시간이 초과했을 때 콜백이 불린다는 겁니다. 즉, 주기를 1.0 초로 설정해놓으면 최초 시간의 1.01 혹은 1.02 초 뒤에 콜백이 불릴 수 있다는 것입니다. 이 점을 잘 고려해서 사용해야 합니다.

[유의사항 2 - 타이머 종료]
타이머를 종료하는 방법은 두 가지가 있습니다.
첫번째는 ecore_timer_del() 을 이용하는 방법이고 두번째는 타이머에 걸어둔 콜백에서 ECORE_CALLBACK_CANCEL 을 리턴하는 방법입니다.  
ecore_timer_del() 은 원하는 시점에 타이머를 종료하는 방법인데, 이 함수의 원형은 아래와 같습니다.
EAPI void *                         
ecore_timer_del(Ecore_Timer *timer) 
ecore_timer_add() 를 사용할 때 리턴받은 포인터를 ecore_timer_del() 에 인자로 넣어주면 됩니다.

그리고
타이머 콜백에서 ECORE_CALLBACK_CANCEL 을 리턴하는 경우 타이머를 더이상 사용하지 않고 폐기시키겠다는 의미입니다. 만약 타이머를 계속 돌리고 싶다면, ECORE_CALLBACK_RENEW 를 리턴하시면 됩니다.

이 두가지 용법을 알고 상황에 맞게 적절하게 사용하셔야 합니다. 

유의사항 3 -  타이머 변수 처리)
이번 유의사항이 가장 중요한 겁니다. 타이머에 대한 포인터를 변수에 저장해놓을 때,
타이머를 삭제한 경우 타이머 포인터를 NULL 로 초기화해줘야 합니다.
예를 들어, 아래와 같은 코드가 있다고 했을 때
Ecore_Timer *my_timer = ecore_timer_add(1.0, _my_timer_cb, NULL);
...
...
if (my_timer)
   ecore_timer_del(my_timer);
ecore_timer_del() 을 실행하는 순간 타이머가 종료됩니다.
그런데 만약 ecore_timer_del() 을 한 뒤에 다시 my_timer 를 체크하고 ecore_timer_del() 을 실행하면 어떻게 될까요?
 Ecore_Timer *my_timer = ecore_timer_add(1.0, _my_timer_cb, NULL);
...
...
if (my_timer)
   ecore_timer_del(my_timer); ---> (1)
...
...
if (my_timer) ---> (2)
   ecore_timer_del(my_timer);
얼핏 생각하면 이미 타이머가 종료되었기 때문에 ecore_timer_del() 은 아무것도 하지 않을 것 같지만, 실제로는 아래와 같은 에러 메시지를 출력합니다.
ERR<6750>:ecore ecore.c:441 _ecore_magic_fail() 
*** ECORE ERROR: Ecore Magic Check Failed!!!
*** IN FUNCTION: ecore_timer_del()
ERR<6750>:ecore ecore.c:451 _ecore_magic_fail()   Input handle is wrong type
    Expected: f7d713f4 - Ecore_Timer (Timer)
    Supplied: 008c2dc1 - <UNKNOWN>
ERR<6750>:ecore ecore.c:454 _ecore_magic_fail() *** NAUGHTY PROGRAMMER!!!
*** SPANK SPANK SPANK!!!
*** Now go fix your code. Tut tut tut!
아주 자극적인 메시지 입니다. EFL 의 창시자인 Rasterman 의 말에 따르면 '자극을 받아서 어플리케이션을 고치도록 이런 메시지를 만들었다!'라고 합니다. 이 메시지를 보면 기분이 언짢아지겠죠...? 일반적인 개발자라면 메시지를 보고 원인을 찾아보고 문제를 해결하려고 하지만, 신기하게도 대부분 이런 경고를 무시합니다.

이 문제가 발생한 원인은, 위 코드에서 (1) 번 과정에서
my_timer 가 가리키는 타이머 자체는 종료시켰지만, 어플리케이션에서 로컬에 저장하고 있는 my_timer 라는 포인터 변수의 값은 그대로 있기 때문입니다. 즉, 타이머를 종료시킨 후에는 my_timer 가 쓸데없는 곳을 가리키게 됩니다. 참고로 이를 dangling pointer 라고 합니다.
그렇기 때문에 (2) 번 과정에서 my_timer 에 쓸데없는 값일지라도 값이 있기 때문에 조건문 안으로 들어가서 ecore_timer_del() 을 수행하게 됩니다. 그런데 my_timer 가 가리키는 곳에는 타이머가 없기 때문에 위와 같은 에러 메시지를 출력합니다.

그러므로 타이머를 종료할 때는 타이머를 가리키는 변수를 항상 NULL 로 초기화해야 합니다.
Ecore_Timer *my_timer = ecore_timer_add();
...
...
if (my_timer)
  {
     ecore_timer_del(my_timer);
     my_timer = NULL;
  }
그리고 ecore_timer_del() 이 아니라 콜백의 리턴값을 이용하여 타이머를 종료할 때도 타이머를 가리키는 변수를 항상 NULL 로 초기화해야 합니다.
Eina_Bool
_my_timer_cb(void *data)
{
   my_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}
이는 비단 EFL 의 문제가 아니라 C 언어를 사용하는 프로그램에서 발생하는 근본적인 문제입니다. 그러므로 앞으로는 이런 종류의 상황에 접했을 때 변수를 적절하게 초기화하는 센스를 발휘하기 바랍니다.

감사합니다. 
[ EFL 게시물 목차 : http://yellowbirds.tistory.com/1 ]