[Step. 10] 이벤트 ( event )

C++/CLI 2010.08.20 09:00 Posted by 흥배
이벤트는 그 이름처럼 이벤트를 처리할 때 사용하는 것으로 델리게이트와 조합하여 사용합니다.

보통 이벤트를 설명할 때 button 클래스를 예제로 사용하므로 저도 이것을 사용하여 간단하게 설명하겠습니다.^^

button 클래스의 여러 이벤트 중 clicked 이벤트를 구현할 때 이벤트로 불러질 클라이언트는 OnClick()이라는 멤버함수를 정의한 후 이것을 button 클래스의 clicked에 등록합니다.

 

delegate void ClickHandler();
ref class button {
public:
    event ClickHandler^ Clicked;
    button() {
        btnForTest = this;
    }
public:
    void someoneclicked() {
        Clicked();
    }
    static button^ btnForTest;
};

ref class Client {
private:
    button^ btn;
public:
    Client() {
        btn = gcnew button;
        btn->Clicked += gcnew ClickHandler(this, &Client::OnClick);
    }
private:
    void OnClick() {
        Console::WriteLine("someone clicked.");
    }
};
int main()
{
    Client^ client = gcnew Client;
   
button::btnForTest->someoneclicked();
    return 0;
}
< 출처 : http://cppcli.shacknet.nu/cli:event >
 
 
event는 몇 개의 제약이 있습니다. 
1. Clicked 호출은 button 밖에 할 수 없습니다. 
2. 클라이언트가 Clicked에 대한 조작은 += -=만 할 수 있으며 Clicked() Clicked = nullptr 같은 
것은 할 수 없습니다.
 
 
참고
http://cppcli.shacknet.nu/cli:event






저작자 표시
신고

Concurrency Runtime
– 동기화 객체 2. ( event )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 지난 글에 이어서 Concurrency Runtime 에서 제공하는 동기화 객체에 대해서 알아보도록 하겠습니다.

이번 글에서는 event 에 대해서 알아보겠습니다.

 

event

 event 는 어떤 상태를 나타낼 수 있는 동기화 객체입니다.

 event 는 어떤 공유 데이터에 대한 접근을 동기화하는 것이 아니라 실행의 흐름을 동기화합니다.

 어떤 작업이 완료되기를 기다렸다가 진행될 수 있고, 어떤 상태를 외부에 알릴 수도 있습니다.

 event 는 기본적으로 2가지 상태를 갖습니다. 설정된 상태( 시그널된 상태라고도 합니다. )와 설정되지 않은 상태를 가지고 있고, 플래그의 역할을 할 수 있습니다.

 event 는 자신의 상태가 설정될 때까지 기다리는 기능을 가지고 있어서 실행의 흐름을 동기화할 수 있습니다.

  Windows API 의 이벤트 객체와 유사합니다. 다른 점은 event 는 협조적이라는 것입니다. wait() 에 의해 대기하고 있을 때, 대기 중인 스레드 자원을 다른 작업에 사용하여 더욱 효율적인 스케쥴링을 하게 됩니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수들에 대해 알아보도록 하겠습니다.

 

void set()

 event 를 설정합니다.

 wait() 로 기다리던 event 는 계속해서 진행하게 됩니다.

 

void reset()

 event 를 설정하지 않습니다. 즉, 초기 상태로 되돌립니다.

 

size_t wait(unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE)

 event 가 설정될 때까지 기다립니다.

 매개변수인 _Timeout 은 기다리는 최대 시간을 지정할 수 있습니다. 기본 매개변수인  COOPERATIVE_TIMEOUT_INFINITE 는 무한대를 나타냅니다.

 event 가 설정되어 기다리는 것을 멈추고 계속 진행될 때, wait() 는 0 을 반환합니다. 반면에 지정한 최대 시간을 초과하여 기다리는 것을 멈추고 계속 진행될 때는 COOPERATIVE_WAIT_TIMEOUT( 0xffffffff ) 를 반환합니다.

 

static size_t __cdecl wait_for_multiple(event ** _PPEvents, size_t _Count, bool _FWaitAll, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE);

 정적 멤버 함수로 여러 개의 event 를 기다립니다.

 매개 변수인 _PPEvents 는 event 의 포인터 배열입니다.

 매개 변수인 _Count 는 기다릴 event 의 개수입니다.

 매개 변수인 _FWaitAll 은 지정된 event 들이 모두 설정될 때까지 기다릴 것인지 여부입니다. false 를 지정하면 하나의 event 라도 설정되면 기다리는 것을 멈추고 계속 진행됩니다.

 마지막 매개 변수인 _Timeout 은 최대 시간입니다. 기본 매개 변수인 COOPERATIVE_TIMEOUT_INFINITE 는 무한대를 나타냅니다.

 매개 변수 중 _FWaitAll 을 false 로 지정하고, 하나의 event 가 설정되었을 때, 설정된 event 의 _PPEvents 로 지정된 배열의 인덱스가 반환됩니다.

 _FWaitAll 을 true 로 지정했을 경우에 모든 event 가 설정되었을 때에는 COOPERATIVE_WAIT_TIMEOUT 이 아닌 값이 반환됩니다.

 Windows API 의 이벤트 객체를 사용할 때 함께 사용하는 WaitForMultipleObject() 와 유사합니다.

 

예제

 Windows API 의 이벤트 객체와 어떻게 다른지 알아볼 수 있는 예제를 구현해보겠습니다.

 

시나리오

 우선 최대 2개의 작업이 동시에 수행될 수 있도록 설정합니다.

 그리고 하나의 event 를 생성한 후, 5개의 작업을 병렬로 처리합니다. 각 작업마다 생성한 event 가 설정될 때까지 기다리도록 합니다.

 메인 스레드에서 1 초 후에 그 event 를 설정합니다.

 같은 작업을 Windows API 의 이벤트 객체를 사용해서 구현합니다.

 

코드

// 코드의 출처는 msdn 입니다.
#include <windows.h>
#include <concrtrm.h>
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace Concurrency;
using namespace std;

// Demonstrates the usage of cooperative events.
void RunCooperativeEvents()
{
   // An event object.
   event e;

   // Create a task group and execute five tasks that wait for
   // the event to be set.
   task_group tasks;
   for (int i = 0; i < 5; ++i)
   {
      tasks.run([&] {
         // Print a message before waiting on the event.
         wstringstream ss;
         ss << L"\t\tContext " << GetExecutionContextId() 
            << L": waiting on an event." << endl; 
         wcout << ss.str();

         // Wait for the event to be set.
         e.wait();

         // Print a message after the event is set.
         ss = wstringstream();
         ss << L"\t\tContext " << GetExecutionContextId() 
            << L": received the event." << endl; 
         wcout << ss.str();
      });
   }

   // Wait a sufficient amount of time for all tasks to enter 
   // the waiting state.
   Sleep(1000L);

   // Set the event.

   wstringstream ss;
   ss << L"\tSetting the event." << endl; 
   wcout << ss.str();

   e.set();

   // Wait for all tasks to complete.
   tasks.wait();
}

// Demonstrates the usage of preemptive events.
void RunWindowsEvents()
{
   // A Windows event object.
   HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, TEXT("Windows Event"));

   // Create a task group and execute five tasks that wait for
   // the event to be set.
   task_group tasks;
   for (int i = 0; i < 5; ++i)
   {
      tasks.run([&] {
         // Print a message before waiting on the event.
         wstringstream ss;
         ss << L"\t\tContext " << GetExecutionContextId() 
            << L": waiting on an event." << endl; 
         wcout << ss.str();

         // Wait for the event to be set.
         WaitForSingleObject(hEvent, INFINITE);

         // Print a message after the event is set.
         ss = wstringstream();
         ss << L"\t\tContext " << GetExecutionContextId() 
            << L": received the event." << endl; 
         wcout << ss.str();
      });
   }

   // Wait a sufficient amount of time for all tasks to enter 
   // the waiting state.
   Sleep(1000L);

   // Set the event.

   wstringstream ss;
   ss << L"\tSetting the event." << endl; 
   wcout << ss.str();

   SetEvent(hEvent);

   // Wait for all tasks to complete.
   tasks.wait();

   // Close the event handle.
   CloseHandle(hEvent);
}

int wmain()
{
   // Create a scheduler policy that allows up to two 
   // simultaneous tasks.
   SchedulerPolicy policy(1, MaxConcurrency, 2);

   // Attach the policy to the current scheduler.
   CurrentScheduler::Create(policy);

   wcout << L"Cooperative event:" << endl;
   RunCooperativeEvents();

   wcout << L"Windows event:" << endl;
   RunWindowsEvents();
}

[ 코드1. event 와 Windows API 이벤트 객체와의 차이 ]

 event 의 경우 5 개의 작업 중 동시에 2개가 수행되도록 하여 2 개의 작업이 기다리고 있을 때, 기다리는 스레드 자원은 다른 작업을 하게 됩니다. 이것이 바로 협력적인 스케쥴링입니다.

 반면에 Windows API 의 이벤트 객체를 사용할 경우, 2 개의 작업이 다시 시작될 때까지 스레드 자원은 낭비를 하게 됩니다.

 

 

[ 그림1. event와 Windows API 이벤트 객체와의 차이 예제 결과 ]

[ 그림1. event와 Windows API 이벤트 객체와의 차이 예제 결과 ]

 

마치는 글

 Concurrency Runtime 에서 제공하는 동기화 객체인 event 까지 알아보았습니다. 이렇게 해서 제공하는 모든 동기화 객체를 알아보았습니다.

 동기화 객체도 알아보았으니 이제 사용자 정의 message block 을 구현할 준비가 다 된 것 같습니다.

 다음 글에서는 제공하는 message block 이 외에 사용자가 구현사여 사용하는 message block 에 대해서 알아보겠습니다.

신고
병행 런타임 관련 두번째 예제로 event를 이용합니다. 여기서 이벤트는 뮤텍스 등과 같은 동기화 개체의 하나로 기존 Win32에서의 수동리셋이벤트(manual-reset event)와 같은 것을 말합니다.

기존 이벤트와 병행 런타임에서 제공하는 이벤트에는 한가지 중요한 차이점이 있습니다. 새로운 이벤트는 병행 런타임을 인식하여 작업이 블록되는 경우 다른 작업에 스레드를 양보(yield)합니다. 무조건 선점형으로 동작하는 기존 Win32 이벤트보다 더 지능적이고 효율적으로 동작하는 것이죠.

자, 그럼 코드를 살펴봅시다. 이 예제에서는 스케줄러의 병렬성을 2로 제한하고 그보다 많은 수의 작업을 병행 수행할 때, 기존 Win32 이벤트와 병행 런타임 이벤트를 각각 활용하는 경우 어떤 차이가 있는지 보여줍니다.

    1 // event.cpp : Defines the entry point for the console application.

    2 //

    3 // compile with: /EHsc

    4 #include <windows.h>

    5 #include <concrt.h>

    6 #include <concrtrm.h>

    7 #include <ppl.h>

    8 

    9 using namespace Concurrency;

   10 using namespace std;

   11 

   12 class WindowsEvent

   13 {

   14     HANDLE m_event;

   15 public:

   16     WindowsEvent()

   17         :m_event(CreateEvent(NULL,TRUE,FALSE,TEXT("WindowsEvent")))

   18     {

   19     }

   20 

   21     ~WindowsEvent()

   22     {

   23         CloseHandle(m_event);

   24     }

   25 

   26     void set()

   27     {

   28         SetEvent(m_event);

   29     }

   30 

   31     void wait(int count = INFINITE)

   32     {

   33         WaitForSingleObject(m_event,count);

   34     }

   35 };

   36 

   37 template<class EventClass>

   38 void DemoEvent()

   39 {

   40     EventClass e;

   41     volatile long taskCtr = 0;

   42 

   43     //태스크그룹을 생성하고 여러 태스크 사본을 스케줄링합니다.

   44     task_group tg;

   45     for(int i = 1;i <= 8; ++i)

   46         tg.run([&e,&taskCtr]{           

   47 

   48       //작업 부하를 시뮬레이션합니다.

   49             Sleep(100);

   50 

   51             //태스크 카운터를 증가시킵니다.

   52             long taskId = InterlockedIncrement(&taskCtr);

   53             printf_s("\tTask %d waiting for the event\n", taskId);

   54 

   55             e.wait();

   56 

   57             printf_s("\tTask %d has received the event\n", taskId);

   58 

   59     });

   60 

   61     //이벤트를 셋하기 전에 충분히 시간을 보냅니다.

   62     Sleep(1500);

   63 

   64     printf_s("\n\tSetting the event\n");

   65 

   66     //이벤트를 셋

   67     e.set();

   68 

   69     //작업들의 완료를 대기

   70     tg.wait();

   71 }

   72 

   73 int main ()

   74 {

   75     //스레드 둘만을 활용하는 스케줄러를 생성합니다.

   76     CurrentScheduler::Create(SchedulerPolicy(2, MinConcurrency, 2, MaxConcurrency, 2));

   77 

   78     //협력적 이벤트를 사용할 경우, 모든 작업들이 시작됩니다.

   79     printf_s("Cooperative Event\n");

   80     DemoEvent<event>();

   81 

   82     //기존 이벤트를 사용하면, Win7 x64 환경이 아닌한

   83     //ConcRT가 블록 상황을 인식하지 못하여 첫 두 작업만이 시작됩니다.

   84     printf_s("Windows Event\n");

   85     DemoEvent<WindowsEvent>();

   86 

   87     return 0;

   88 }


WindowsEvent 클래스는 기존 Win32 이벤트를 위한 랩퍼(wrapper) 클래스입니다. DemoEvent 함수 템플릿이 사용할 이벤트 형을 템플릿 인자로 받아 실제 작업을 하는 놈입니다. PPL(Parallel Patterns Library)의 task를 이용해 8개 작업을 만들고 각각에서 이벤트를 기다리도록 하고 있습니다.

결과는 다음과 같이 나올 겁니다.


병렬성이 둘로 제한되는 상황에서도 병행 런타임의 이벤트를 사용할 경우 각 작업이 블록될 경우 다른 작업에 스레드를 양보하기 때문에 8개의 작업이 모두 시작되는 것을 확인하실 수 있습니다. 반면, 기존 이벤트의 경우 두 작업만이 시작되었다가 이벤트를 받고 두 작업이 종료된 후에나 다른 작업들이 시작되는 것을 확인하실 수 있습니다.
신고