Search

'Visual Studio 2010'에 해당되는 글 260건

  1. 2010.08.24 Asynchronous Agents Library – message block 9. ( custom )
  2. 2010.08.18 Concurrency Runtime – 동기화 객체 2. ( event )
  3. 2010.08.16 M, V 그리고 C의 각방생활(12) - 테스팅 그거, 아무나 하나? 1
  4. 2010.08.15 Concurrency Runtime – 동기화 객체 1. ( critical_section & reader_writer_lock )
  5. 2010.08.12 Asynchronous Agents Library – message block 8. ( timer )
  6. 2010.08.11 M, V 그리고 C의 각방생활(11) - jqGrid로 데이터 추가,편집,삭제해보기 28
  7. 2010.08.09 VS2010 C++ 프로젝트의 디렉토리 설정 2
  8. 2010.08.09 Asynchronous Agents Library – message block 7. ( join & multitype_join )
  9. 2010.08.06 Asynchronous Agents Library – message block 6. ( choice )
  10. 2010.08.05 WPF 리본 컨트롤 RTW 출시 4
  11. 2010.08.03 Asynchronous Agents Library – message block 5. ( transformer )
  12. 2010.08.02 [Upgrade to VC++ 10] _WIN32_WINNT 버전 문제 6
  13. 2010.07.31 Asynchronous Agents Library – message block 4. ( call )
  14. 2010.07.28 [세미나] Visual Studio Camp #1
  15. 2010.07.28 ASP.NET MVC 3 Preview 1 이 릴리즈 되었습니다. 1
  16. 2010.07.27 Asynchronous Agents Library – message block 3. ( overwrite_buffer & single_assignment ) 1
  17. 2010.07.26 [MFC/윈도우 7 멀티터치] #6 : 예제 코드 올립니다 3
  18. 2010.07.18 Asynchronous Agents Library – message block 2. ( unbounded_buffer ) 2
  19. 2010.07.15 M, V 그리고 C의 각방생활(10) - jqGrid를 이용한 paging과 sorting 2
  20. 2010.07.14 M, V 그리고 C의 각방생활(9) - jqGrid 사용해보자

Asynchronous Agents Library
– message block 9. ( custom )

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

 

시작하는 글

 아마도 이 글이 Asynchronous Agents Library( 이하 AAL ) 에 관련된 마지막 글이라고 생각이 됩니다. 더 추가적으로 적을 내용이 생기면 더 적어 보도록 하겠습니다.

 이전 글까지 AAL 에서 제공하는 message block 들을 알아보았지만, 제공되는 것들이 마음에 들지 않는 분들은 직접 message block 을 만들어 사용할 수 있습니다.

 직접 message block 을 만들어 사용하는 것이 그리 간단하지만은 않기 때문에 이번 글이 좀 길어질 것 같습니다. 천천히 읽어주시기 바랍니다.

 

Message block 의 종류와 인터페이스

 우선 message block 정의에 앞서 message block 의 종류에 대해서 알아보는 것이 좋겠습니다. 이미 이전 글들에서 본 제공되는 message block 들을 보시면서 아셨겠지만, 한 번 더 짚고 넘어가도록 하겠습니다.

 Message 를 보내는 역할을 하는 message block 을 source block 이라 하고, message 를 받는 역할을 하는 역할을 하는 message block 을 target block 이라고 합니다.

 이 두 가지 종류의 block 들의 기본적인 행동들을 알리기 위해서 source block 들은 ISource 인터페이스를, target block 들은 ITarget 인터페이스를 상속받아 인터페이스 메소드들을 정의해야 합니다.

 

Message block 의 기본 클래스들

 위에서 언급한 ISource 인터페이스나 ITarget 인터페이스를 직접 상속받아 정의해도 되지만, AAL 에서는 그 인터페이스를 상속받아 정의하기 쉽도록 해주는 기본 클래스 3 가지를 제공합니다.

  • source_blockISource 인터페이스를 상속받고, 다른 block 에 message 를 보냅니다.
  • target_blockITarget 인터페이스를 상속받고, 다른 block 으로부터 message 를 받습니다.
  • propagator_blockISource 인터페이스와 ITarget 인터페이스를 상속받고, 다른 block 들과 message 를 보내고 받습니다.

 

 AAL 에서는 사용자 정의 message block 을 정의할 때, 인터페이스를 직접 상속받기 보다는 위의 기본 클래스들을 상속받아 정의하는 것을 권고하고 있습니다.

 

연결 관리 및 message 관리를 위한 템플릿 매개변수에 사용할 수 있는 클래스 템플릿

  위의 3가지 기본 클래스들은 클래스 템플릿으로, message block 들간의 연결을 어떻게 관리할 것인지, message 들을 어떻게 관리할 것인지에 대한 정보를 템플릿 매개변수를 통해 지정할 수 있습니다.

 AAL 은 연결 관리를 위한 2가지의  클래스 템플릿을 제공하고 있습니다.

  • single_link_registry – source 나 target 의 하나의 연결만 허용.
  • multi_link_registry – source 나 target 의 여럿의 연결을 허용.

 

 예를 들면, AAL 에서 제공하는 transformer 는 출력을 하나의 연결만 허용하는 single_link_registry 를 사용하고, 입력은 여럿의 연결을 허용하는 multi_link_registry 를 사용하고 있습니다.

template<class _Input, class _Output>
class transformer : public propagator_block<single_link_registry<ITarget<_Output>>, multi_link_registry<ISource<_Input>>>

[ 코드1. transformer 에서 사용되는 single_link_registry 와 multi_link_registry ]

 

 또한 message 관리를 위한 하나의 클래스 템플릿을 제공합니다.

  • ordered_mesage_processor – message 를 받는 순서대로 처리하도록 허용.

 

재정의해야 할 멤버 함수들

 위에 말한 기본 클래스들을 상속한 클래스는 다음과 같은 멤버 함수를 재정의해야 합니다.

 

source_block 을 상속한 클래스가 재정의해야 할 멤버 함수들

  • propagate_to_any_targets()
  • accept_message()
  • reserve_message()
  • consume_message()
  • release_message()
  • resume_propagation()

 

propagate_to_any_targets()

 입력 message 나 출력 message 를 비 동기 또는 동기적으로 처리하기 위해 런타임으로부터 호출됩니다.

 

accept_message()

 Message 를 수락하기 위해 target block 으로부터 호출됩니다.

 

reserve_message(), consume_message(), release_message(), resume_propagation()

 이 멤버 함수들은 message 를 예약하는 기능을 제공합니다.

 target block 은 message 를 제공받을 때와 message 를 나중에 사용하기 위해 예약해야 할 때 reserve_message() 를 호출합니다.

 target block 은 하나의 message 를 예약한 후에, message 를 사용하기 위해 consume_message() 를 호출하거나 예약을 취소하기 위해 release_message() 를 호출할 수 있습니다.

 consume_message() 는 accept_message() 와 함께 사용되어 message 의 소유권을 보내거나 message 의 복사본을 보내도록 할 수 있습니다.

 예를 들어, unbounded_buffer 와 같은 message block 은 오직 하나의 target 에만 message 를 보냅니다. 즉, message 의 소유권을 target 에 보냅니다. 그렇기 때문에 보낸 후, message block 내에는 보낸 message 가 남아있지 않습니다.

 반면에 overwrite_buffer 와 같은 message block 은 각 연결된 target 에 각각 message 를 제공합니다. 즉, message 의 복사본을 보냅니다, 그래서 보낸 후, message block 내에 보낸 message 가 여전히 존재하게 됩니다.

 target block 이 예약된 message 를 사용하거나 취소한 후에, 런타임은 resume_propagation() 을 호출합니다. resume_propagation() 은 큐( queue )의 다음 message 를 시작으로 message 의 전달을 계속해서 진행시킵니다.

 

target_block 을 상속한 클래스가 재정의해야 할 멤버 함수들

  • propagate_message()
  • send_message() ( option )

 

propagate_message(), send_message()

 런타임은 다른 block 으로부터 현재의 block 으로 message 를 비 동기적으로 받기 위해 propagate_message() 를 호출합니다.

send_message() 는 비 동기적이 아니라 동기적으로 message 를 보내는 것을 제외하면 propagate_message() 와 비슷합니다.

 기본적으로 send_message() 의 정의는 모든 입력 message 를 거부하도록 구현되어 있습니다.

 만약 message 가 target block 에 설정된 필터 함수를 통과하지 못하면 send_message() 나 propagate_message() 를 호출하지 않습니다.

 

propagator_block 을 상속한 클래스가 재정의해야 할 멤버 함수들

  propagator_blocksource_blocktarget_block 의 특징을 모두 상속하므로 두 클래스에 대해 재정의해야 할 멤버 함수들을 모두 재정의해야 합니다.

 

예제: priority_buffer

 priority_buffer 는 우선 순위를 기준으로 순서를 정하는 사용자 정의 message block 입니다. 여럿의 message 들을 받을 때 우선 순위대로 처리할 때 사용하기 유용합니다.

 Message 큐를 가지고 있고, source 와 target 의 역할을 하며, 여럿의 source 와 여럿의 target 을 연결할 수 있기 때문에 unbounded_buffer 와 비슷합니다.

 그러나 unbounded_buffer 는 message 를 받는 순서를 기반으로 message 를 전달한다는 것이 다릅니다.

 priority_buffer 는 우선 순위와 데이터를 같이 전달해야 하므로 우선 순위 타입과 데이터의 타입을 포함하는 tuple 타입의 message 를 전달합니다.

 priority_buffer 는 2개의 message 큐를 관리합니다. 입력 message 를 위한 std::priority_queue 와 출력 message 를 위한 std::queue 입니다.

 priority_buffer 의 정의를 위해 propagator_block 을 상속하고 7개의 멤버 함수들을 정의해야하고, link_target_notification() 과 send_message() 를 재정의해야 합니다.

 또한 2개의 public 멤버 함수인 enqueue() 와 dequeue() 를 정의합니다. private 멤버 함수로 propagate_priority_order() 를 정의합니다.

 

코드

#include <agents.h>
#include <queue>

// 우선 순위를 비교할 비교 함수 객체.
namespace std
{
	template< class Type, class PriorityType >
	struct less< Concurrency::message< tuple< PriorityType, Type > >* >
	{
		typedef Concurrency::message< tuple< PriorityType, Type > >		MessageType;

		bool operator()( const MessageType* left, const MessageType* right ) const
		{
			// message 가 tuple 이므로 get() 을 사용.
			return ( get< 0 >( left->payload ) < get< 0 >( right->payload ) );
		}
	};

	template< class Type, class PriorityType >
	struct greater< Concurrency::message< tuple< PriorityType, Type > >* >
	{
		typedef Concurrency::message< tuple< PriorityType, Type > >		MessageType;

		bool operator()( const MessageType* left, const MessageType* right ) const
		{
			return ( get< 0 >( left->payload ) > get< 0 >( right->payload ) );
		}
	};
}

namespace Concurrency
{
	// Type - 데이터 타입, PriorityType - 우선 순위 타입, PredicatorType - 비교 함수 객체
	// source 와 target 역할을 하므로 propagator_block 을 상속받고, 다중 입력 연결과 다중 출력 연결을 허용하므로 모두 multi_link_registry 를 사용.
	template< class Type, typename PriorityType = int, typename PredicatorType = std::less< message< std::tuple< PriorityType, Type > >* > >
	class priority_buffer
		: public propagator_block< multi_link_registry< ITarget< Type > >, multi_link_registry< ISource< std::tuple< PriorityType, Type > > > >
	{
	public:
		~priority_buffer()
		{
			// 연결을 모두 해제하기 위해 propagator_block 의 remove_network_links() 를 사용.
			this->remove_network_links();
		}

		priority_buffer()
		{
			// source 와 target 을 초기화 하기 위해 propagator_block 의 initialize_source_and_target() 을 사용.
			this->initialize_source_and_target();
		}

		priority_buffer( filter_method const& filter )
		{
			this->intialize_source_and_target();

			// 필터 함수를 등록하기 위해 target_block 의 register_filter() 를 사용.
			this->register_filter( filter );/
		}

		priority_buffer( Scheduler& scheduler )
		{
			this->initialize_source_and_target( &scheduler );
		}

		priority_buffer( Scheduler& scheduler, filter_method const& filter )
		{
			this->initialize_source_and_target( &scheduler );
			this->register_filter( filter );
		}

		priority_buffer( ScheduleGroup& schedule_group )
		{
			this->initialize_source_and_target( nullptr, &schedule_group );
		}

		priority_buffer( ScheduleGroup& schedule_group, filter_method const& filter )
		{
			this->initialize_source_and_target( nullptr, &schedule_group );
			this->register_filter( filter );
		}

		// 공개 멤버 함수들.
		bool enqueue( Type const& item )
		{
			return Concurrency::asend< Type >( this, item );
		}

		Type dequeue()
		{
			return Concurrency::receive< Type >( this );
		}

	protected:
		// message 처리하기 위해 런타임으로 부터 호출됨. message 를 입력 큐에서 출력 큐로 이동하고, 전달.
		virtual void propagate_to_any_targets( message< _Target_type >* )
		{
			message< _Source_type >* input_message = nullptr;

			{
				critical_section::scoped_lock lock( this->input_lock );

				// 보낼 message 를 우선 순위 큐에서 꺼냄.
				if( this->input_messages.size() > 0 )
				{
					input_message = this->input_messages.top();
					this->input_messages.pop();
				}
			}

			if( nullptr != input_message )
			{
				// 입력된 message 는 우선 순위와 데이터를 같이 가지고 있으므로 데이터만 가진 message 로 가공하여 출력 큐에 넣음.
				message< _Target_type >* output_message = new message< _Target_type >( get< 1 >( input_message->payload ) );
				this->output_messages.push( output_message );

				delete input_message;

				if( this->output_messages.front()->msg_id() != output_message->msg_id() )
				{
					return;
				}
			}

			// message 보내기.
			this->propagate_priority_order();
		}

		// target block 이 message 를 받기 위해 호출함. 이 코드에서는 출력 큐에서 message 를 제거해서 소유권을 이전.
		virtual message< _Target_type >* accept_message( runtime_object_identity msg_id )
		{
			message< _Target_type >* message = nullptr;

			if( !this->output_messages.empty() && this->output_messages.front()->msg_id() == msg_id )
			{
				message = this->output_messages.front();
				this->output_messages.pop();
			}

			return message;
		}

		// target block 이 제공된 message 를 예약하기 위해 호출. 이 코드에서는 전달 가능 여부를 확인.
		virtual bool reserve_message( runtime_object_identity msg_id )
		{
			return ( !this->output_messages.empty() && this->output_messages.front()->msg_id() == msg_id );
		}

		// target block 이 제공된 message 를 사용하기 위해 호출. 이 코드에서는 message 전달.
		virtual message< Type >* consume_message( runtime_object_identity msg_id )
		{
			return this->accept_message( msg_id );
		}

		// target block 이 예약된 message 를 취소하기 위해 호출. 이 코드에서는 아무 역할을 하지 않음.
		virtual void release_message( runtime_object_identity msg_id )
		{
			if( this->output_messages.empty() || this->output_messages.front()->msg_id() != msg_id )
			{
				throw message_not_found();
			}
		}

		// 예약된 message 처리 후, 계속해서 진행.
		virtual void resume_propagation()
		{
			if( this->output_messages.size() > 0 )
				this->async_send( nullptr );
		}

		// 새로운 target 이 연결되었음을 알림.
		virtual void link_target_notification( ITarget< _Target_type >* )
		{
			// 이미 예약된 message 가 있으면 전달하지 않음.
			if( this->_M_pReservedFor != nullptr )
				return;

			// message 보내기.
			this->propagate_priority_order();
		}

		// 비 동기적으로 전달. propagator_block 의 propagate() 에 의해 호출됨.
		virtual message_status propagate_message( message< _Source_type >* message, ISource< _Source_type >* source )
		{
			message = source->accept( message->msg_id(), this );

			if( nullptr != message )
			{
				{
					critical_section::scoped_lock lock( this->input_lock );
					this->input_messages.push( message );
				}

				this->async_send( nullptr );

				return accepted;
			}else
				return missed;
		}

		// 동기적으로 전달. propagator_block 의 send() 에 의해 호출됨.
		virtual message_status send_message( message< _Source_type >* message, ISource< _Source_type >* source )
		{
			message = source->accept( message->msg_id(), this );

			if( nullptr != message )
			{
				{
					critical_section::scoped_lock lock( this->input_lock );
					this->input_messages.push( message );
				}

				this->sync_send( nullptr );

				return accepted;
			}else
				return missed;
		}

	private:
		// message 를 보내는 함수.
		void propagate_priority_order()
		{
			// 이미 예약된 message 가 있으면 전달하지 않음.
			if( nullptr != this->_M_pReservedFor )
				return;

			// 출력 큐의 모든 message 를 보냄.
			while( !this->output_messages.empty() )
			{
				message< _Target_type >* message = this->output_messages.front();

				message_status status = declined;

				// 연결된 target 을 순회하면서 message 를 전달.
				for( target_iterator iter = this->_M_connectedTargets.begin();
					nullptr != *iter;
					++iter )
				{
					ITarget< _Target_type >* target = *iter;
					status = target->propagate( message, this );

					if( accepted == status )
						break;

					if( nullptr != this->_M_pReservedFor )
						break;		 
				}

				if( accepted != status )
					break;
			}
		}

	private:
		std::priority_queue<
			message< _Source_type >*,
			std::vector< message< _Source_type >* >,
			PredicatorType >							input_messages;		
		std::queue< message< _Target_type >* >			output_messages;
		critical_section								input_lock;

	private:
		priority_buffer const& operator=( priority_buffer const& );
		priority_buffer( priority_buffer const&  );
	};
}

[ 코드2. priority_buffer 구현 ]

 재정의하는 함수들이 많고, 어디서 어떻게 호출되는지 알기 어려울 수 있습니다. 간단하게 설명하자면 외부로부터 보내는 함수, send() 또는 asend() 에 의해서 message 가 전달될 경우, target block 의 역할을 하게 됩니다. 이 때는 propagate_message() 나 send_message() 중 하나가 호출됩니다.

 반대로 외부로부터 받는 함수, receive() 또는 try_receive() 에 의해서 message 를 전달해야 하는 경우, source block 의 역할을 하게 되고, 나머지 함수들이 호출되게 됩니다.

 이 예제로 완벽하게는 아니더라도 어떻게 사용자 정의 message block 을 구현해야 하는지 아셨을 것이라 생각됩니다.

 

사용 코드

#include <ppl.h>
#include <iostream>
#include "priority_buffer.h"

using namespace Concurrency;
using namespace std;

int main()
{
	priority_buffer< int > buffer;

	parallel_invoke(
		[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 1, 12 ) ); },
		[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 3, 36 ) ); },
		[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 2, 24 ) ); } );

	for( unsigned int i = 0; i < 75; ++i )
	{
		wcout << receive( buffer ) << L' ';
		if( ( i + 1 ) % 25 == 0 )
			wcout << endl;
	}
}

[ 코드3. priority_buffer 를 사용하는 예제 ]

 parallel_invoke() 를 사용하기 때문에 순서를 입력되는 순서를 보장할 수 없습니다. 하지만 receive() 를 사용하여 message 를 출력해 보면 우선 순위가 큰 순서대로 출력되는 것을 알 수 있습니다.


[ 그림1. priority_buffer 를 사용하는 예제 결과 ]

[ 그림1. priority_buffer 를 사용하는 예제 결과 ]


 

마치는 글

 사용자 정의 message block 을 구현하는 것을 마지막으로 AAL 에 관련된 글을 마무리합니다. AAL 에 관련해 새로운 소식이 있으면 그 때 다시 AAL 에 관련된 글을 작성하도록 하겠습니다.

 다음 글은 AAL 을 포함하는 Concurrency Runtime 에서 제공하는 작업 스케줄러에 대해서 작성해 볼 예정입니다. 작업 스케줄러는 AAL 의 message block 과 함께 유용하게 사용할 수 있으므로 AAL 에 관심이 많으신 분들도 기대하셔도 좋을 것 같습니다.

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

VC++ 10 Concurrency Runtime 2010. 8. 18. 08:30 Posted by 알 수 없는 사용자

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 에 대해서 알아보겠습니다.

정말이지, 테스팅 그거 아무나 하는거 아니죠. 특히 저처럼 게으른 놈은 발을 들여놓기가 무서울때도 있습니다.
고객분들은 빠른 결과물을 얻길 원하시고, 그 고객이 여럿이면 모두가 자기의 일이 우선이니 빨리 좀 해달라고 아우성 거릴때가 많습니다. 가뜩이나 개발로도 벅찬 시간인데, 테스트라뇨.. 에잇!
하지만, 그렇게 작업을 한 후 스테이징(Staging Server - 라이브 서버에 반영하기 전 배포하여 테스트하는 서버입니다^^) 에 적용해놓으면 테스트팀에서는 온갖 방법으로(정말 어처구니 없는 입력값으로 마구 공격(?)해 들어오시죠) 테스트를 한 후 결과물들을 전달해주시죠. 그것 예외처리하는 것으로 인해 또한번의 시간이 소비되고 다시 테스트하고 다시 결과물 받고, 계속 반복되는거죠. 그렇게되면, 처음에 빨리 개발했던 시간이 무용지물이 되어버리는 것이죠.

신입시절에(지금도 신입 짬밥이죠;;) 과장님 한 분이, '야, 어떻게 너는 일을 주면 생각없이 컴퓨터로 바로 달려들어 개발하냐?' 라고 한 말씀 해주셨습니다. 먼저 그림을 그려보라고, 이면지 많잖아. 없어? 내가 줄까? 그러시며,  흐름을 생각하며, 어떤식으로 해야할지 먼저 그림을 그리라고요.
테스팅하는 것도 먼저 그림을 그려보는 작업 같습니다. 이렇게 개발을 하면 원하는 결과값이 나오겠지하며, 테스트와 병행하며 원하는 결과값이 맞게 나오는지 확인해가며 개발을 해나가는 거죠.



단위 테스트 프레임워크를 사용해보자


닷넷 프레임워크를 위한 테스트 프레임워크가 많습니다. 다들 아시는 NUnit, xUnit, MbUnit 등이 있죠. 하지만, 저 게으른 것 다들 아실겁니다. 그래서! 비주얼 스튜디오 단위 테스트를 이용하겠습니다. 위 단위 테스트 사이트들 들어가서 다운받고 설치하고, 에휴~^^;

ASP.NET MVC 프로젝트를 생성하게 되면 아래의 화면과 같이 단위 테스트를 할지 여부를 묻는 대화상자가 나옵니다. 예(Yes)를 선택하면, 프로젝트가 생성될때 테스트 프로젝트도 같이 생성하게 됩니다.


테스트 프로젝트에는 Controllers라는 폴더가 있고, 그 안에는 샘플프로젝트의 Home컨트롤러와 Account컨트롤러를 테스트하기 위한 HomeControllerTest.cs와 AccountControllerTest.cs 가 있습니다. HomeControllerTest를 열어보면 다음과 같습니다.


Microsoft.VisualStudio.TestTools.UnitTesting 네임스페이스가 임포트 되어 있는 것이 보이고, [TestClass], [TestMethod] 가 눈에 띄네요. 주석을 보니,

TestClass : 테스트 메서드가 포함된 클래스를 식별하는데 사용됩니다.
TestMethod : 테스트 메서드를 식별하는데 사용됩니다.

이렇다네요.^^



테스트와 함께 하시겠습니까?


다음은 TelDirController 소스입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace UsingTest.Controllers
{
    public class TelDirController : Controller
    {
        //
        // GET: /TelDir/

        public ActionResult Index()
        {
            return View();
        }

        //
        // GET: /TelDir/Details/5

        public ActionResult Details(int id)
        {
            return View();
        }       
    }
}

아무것도 처리하지 않은 깨끗한(?) 소스입니다. Index(), Details() 라는 두 액션메쏘드가 있고, 두 메쏘드 모두 View를 리턴하고 있습니다.

그럼, TelDirController를 위한 테스트를 생성해보겠습니다.



테스트 만들기


테스트 프로젝트의 Controllers를 오른쪽 버튼으로 클릭한 후 추가 -> 새 테스트를 선택합니다.


테스트하려는 컨트롤러 이름 뒤에 Test 만 붙여서 이름을 만들겠습니다. TelDirControllerTest 처럼요.^^;


확인 버튼을 클릭하면 다음의 소스가 보이실겁니다^^


지금으로선 불필요한 것들을 모두 닫아놓긴 했는데요, 물론 삭제하셔도 됩니다. 이런 것도 싫다 하시면, Controllers 폴더에서 클래스를 생성한 후 TestClass와 TestMethod 속성, 그리고 UnitTesting 네임스페이스를 임포트시키시면 됩니다.



맛보기 테스트


정말 맛만 보여드리겠습니다.^^;

TelDirController.Details 메쏘드가 정말 "Details" 뷰를 리턴하는지 테스트 해보도록 하겠습니다.
먼저 테스트 메쏘드를 만들어보겠습니다.

[TestMethod]
public void DetailsTest()
{
    var controller = new TelDirController();
    var result = controller.Details(1) as ViewResult;
    Assert.AreEqual("Details", result.ViewName);
}

Assert.AreEqual 은 비교대상의 두 값이 같은지 여부를 나타냅니다. 같으면 성공, 다르면 실패.
ViewResult.ViewName은 컨트롤러에서 리턴받은 뷰명을 나타냅니다.
자~ 테스트를 진행해볼까요?


'솔루션의 모든 테스트 실행' 버튼을 클릭합니다. 단축키는 Ctrl+R,A 라고 친절히 알려주네요^^ 물론 지금은 테스트 케이스가 하나라 이렇게 하지만 모든 테스트 실행 좌측에 있는 버튼인 '현재 컨텍스트의 테스트 실행'은 현재 선택받은 곳, 즉 커서가 위치한 곳의 테스트를 진행합니다.


엥?! 실패하였습니다. 예상 값이 Details 인데 실제값은.. 실제값은.. 없네요;;

TelDirController의 Details 메쏘드를 살펴보죠.

public ActionResult Details(int id)
{
    return View();
}

이렇게 되어 있네요. 잘못된 것은 없어보이는데요. 

Details 뷰를 리턴하려면 두 가지 방법이 있습니다. 위처럼 Details 액션 메쏘드에서 return View() 를 하는 방법(이것은 액션 메쏘드의 이름에서 유추가 됩니다), 다른 하나는 명시적으로 return View("Details") 와 같은 방법입니다.

그럼, 또다른 방법을 택해서 테스트를 돌려보죠.

public ActionResult Details(int id)
{
    return View("Details");
}

결과는


통과~.
이래서, 아~ 테스트시에는 뭐든지 확실하게 명시적으로 표현해줘야하는구나~ 라는 것을 알 수 있습니다.^^



마무리요



이번 포스팅은 간단하게 테스트하는 방법에 대해서 알아봤는데요, 다음은 이 테스팅에 대해서 좀더 자세하게 알아보는 시간을 가져보겠습니다.

맛이 어떠셨나요? 다음에는 더 화끈한 맛을 보여드리도록 하겠습니다^^



참고자료 : http://www.asp.net/mvc/tutorials/creating-unit-tests-for-asp-net-mvc-applications-cs

Concurrency Runtime
– 동기화 객체 1. ( critical_section & reader_writer_lock )

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

 

시작하는 글

 기본적으로 Asynchronous Agents Library( 이하 AAL ) 에서 제공하는 message block 들은 스레드에 안전합니다. 이 말은 내부적으로 동기화를 구현하고 있다는 말입니다.

 하지만 Concurrency Runtime 을 이용한 동시 프로그래밍을 할 때 AAL 의 message block 을 사용하지 않고 일반적인 사용자 변수들을 사용할 수 있습니다. 하지만 이런 변수들은 동기화가 되지 않아 직접 동기화를 해 주어야 합니다.

 이 때 Windows API 의 유저 모드 혹은 커널 모드의 동기화 객체를 사용할 수 있지만, Concurrency Runtime 에서 제공하는 동기화 객체를 쉽게 사용할 수 있습니다.

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

 

critical_section

 임계 영역을 의미하는 객체로, 어떤 영역을 잠그고 풀 수 있습니다. 잠긴 영역에 도달한 다른 스레드는 잠김이 풀릴 때까지 기다려야 합니다. 그러므로 해당 영역을 모두 사용한 후에는 다른 스레드도 사용할 수 있도록 풀어주어야 합니다.

 이 기능은 Windows API 에서 제공하는 유저 모드 동기화 객체인 CRITICAL_SECTION 과 같은 기능을 합니다.

 

멤버 함수

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

 

void lock()

 해당 영역을 잠급니다. 잠근다는 의미는 영역의 접근 권한을 획득한다는 의미도 있습니다. 잠긴 영역에 도달한 다른 스레드들은 잠김이 풀릴 때까지 기다려야 합니다.

 

bool try_lock()

 해당 영역의 접근을 시도합니다. 이미 다른 스레드에 의해 잠겨있더라도 풀릴 때까지 기다리지 않고 반환합니다.

 이미 다른 스레드에 의해 잠겨있다면 false 를, 아니면 true 를 반환합니다.

 

void unlock()

 접근 영역의 접근 권한을 반환합니다. 즉, 잠금을 해제합니다. 이 함수가 호출된 후에 다른 스레드가 이 영역에 접근할 수 있습니다.

 

native_handle_type native_handle()

 native_handle_typecritical_section 의 참조입니다. 결국, 이 함수는 자기 자신의 참조를 반환합니다.

 

범위 잠금

 범위 잠금이란 코드 내의 블럭의 시작부터 블럭이 끝날 때까지 잠그는 것을 말합니다. 이것은 생성자에서 잠그고, 소멸자에서 잠금을 해제하는 원리( RAII – Resource Acquisition Is Initialization )로 구현되어 있으며, 굳이 unlock() 을 호출하지 않아도 된다는 편리함이 있습니다.

 

critical_section::scoped_lock

 critical_section 객체를 범위 잠금 객체로 사용합니다. 생성자로 critical_section 의 참조를 넘겨주어야 합니다. 스택에 할당되고 가까운 코드 블럭이 끝날 때, 소멸자가 호출되어 잠금이 해제됩니다.

 

reader_writer_lock

 reader_writer_lock 은 쓰기 접근 권한과 읽기 접근 권한을 구분하여 획득하고 잠글 수 있는 객체입니다.

한 스레드가 쓰기 접근 권한을 획득하여 잠긴다면, 다른 스레드의 모든 쓰기 / 읽기 접근 권한 획득시도가 실패하며 기다리게 됩니다.

 만약 한 스레드가 읽기 접근 권한을 획득하여 잠긴다면, 다른 스레드가 읽기 접근 권한 획득 시도 시, 동시에 읽기 접근 권한을 획득 가능합니다.

 위에 설명한 것처럼 2 가지 종류의 접근 권한이 있지만, 잠기는 것은 단 하나의 객체라는 것을 명심하셔야 합니다. 이 사실을 잊으면 헷갈릴 수 있습니다.

 reader_writer_lock 의 이와 같은 기능은 Windows API 의 SRW( Slim Reader Writer ) 잠금과 유사합니다. 다른 점이 있다면 reader_writer_lock 은 기본 잠금이 쓰기 잠금으로 되어 있고, 잠겼을 때 다른 스레드들이 기다린 순서대로 접근 권한을 얻는다는 것입니다. 즉, Windows SRW 는 기본 잠금이라는 개념이 없고, 기다린 스레드들 중 어떤 순서로 접근 권한을 얻는지 알 수 없습니다.

 

멤버 함수

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

 

void lock()

 기본 잠금으로 쓰기 접근 권한을 획득하고 잠급니다. 이미 다른 스레드에서 접근 권한을 획득하여 잠겨 있다면 해제될 때까지 기다립니다.

 이 함수로 잠길 경우, lock() 과 읽기 접근 권한 획득 함수인 lock_read() 으로 접근 권한을 획득하려 하더라도 획득할 수 없고, 잠금이 해제될 때까지 기다리게 됩니다.

 

bool try_lock()

 쓰기 접근 권한 획득을 시도합니다. 이미 다른 스레드가 접근 권한을 획득하여 잠겼을 경우에는 false 를 반환하고, 접근 권한을 획득하게 되면 true 를 반환합니다. 즉, 대기하지 않습니다.

 

void lock_read()

 읽기 잠금으로 읽기 접근 권한 획득을 합니다. 다른 스레드에서 lock_read() 로 읽기 접근 권한 획득을 시도하면 이미 다른 스레드가 읽기 접근 권한을 획득하였다고 하더라도 동시에 읽기 접근 권한을 획득할 수 있습니다.

 다른 스레드에서 lock() 으로 쓰기 접근 권한 획득을 시도한다면, 이 스레드는 모든 읽기 권한으로 인한 잠금이 해제될 때까지 기다리게 됩니다.

 

bool try_lock_read()

 읽기 접근 권한 획득을 시도합니다. 이미 다른 접근 권한을 획득하여 잠겼을 경우에는 false 를 반환하고, 접근 궈한을 획득하게 되면 true 를 반환합니다. 즉, 대기하지 않습니다.

 

void unlock()

 어떤 잠금이든 해제합니다.

 

범위 잠금

 critical_section 과 마찬가지로 범위 잠금을 지원합니다.

 

reader_writer_lock::scoped_lock

 critical_section 과 같습니다. 다른 점이 있다면 이 잠금 객체는 쓰기 잠금의 범위 잠금 객체입니다.

 

reader_writer_lock:scoped_lock_read

 이 잠금 객체는 읽기 잠금의 범위 잠금 객체입니다.

 

마치는 글

 Concurrency Runtime 에서 제공하는 동기화 객체은 critical_sectionreader_writer_lock 에 대해서 알아보았습니다.

 reader_writer_lock 이 동시 읽기 접근이 가능하므로 critical_section 보다는 상황에 따라 성능이 좋을 수 있습니다.

 다음 글에서는 동기화 객체 중 마지막 하나, event 에 대해서 알아보도록 하겠습니다.

Asynchronous Agents Library – message block 8. ( timer )

VC++ 10 Concurrency Runtime 2010. 8. 12. 08:30 Posted by 알 수 없는 사용자

Asynchronous Agents Library
– message block 8. ( timer )

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

 

시작하는 글

 이번 글에서 설명하는 timer 를 마지막으로 Asynchronous Agents Library( 이하 AAL ) 에서 제공하는 모든 message block 들에 대한 소개를 마칩니다.

  • unbouned_buffer
  • overwrite_buffer
  • single_assignment
  • call
  • transformer
  • choice
  • join
  • multitype_join
  • timer

   각각의 message block 들의 특징을 파악하고 필요한 때에 적합한 것들을 사용하시기 바랍니다.

 

timer< _Type >

 timer 는 message 의 전송을 연기하거나, 주기적으로 같은 message 를 전송하는 역할을 합니다.

 안타깝게도 하나의 timer 는 같은 message 밖에 전송할 수 없게 구현되어 있습니다. 또한 하나의 message block 에만 연결하여 전송할 수 있습니다.

 timercall 또는 transformer 를 연결한다면 좀 더 유연하게 확장할 수 있을 것입니다.

 

상태

 timer 는 총 4가지의 상태를 갖습니다.

  • Initialized – 생성된 후, 아직 시작하지 않은 상태.
  • Started – 시작된 상태.
  • Paused – 일시 정지된 상태.
  • Stopped – 정지한 상태.

 

멤버 함수

 생성자 및 public 인 멤버 함수들 중 인터페이스를 재정의한 함수들을 제외하고 알아보도록 하겠습니다.

 

생성자

timer(unsigned int _Ms, _Type const& _Value, ITarget<_Type> *_PTarget = NULL, bool _Repeating = false)

[ 코드1. timer 의 생성자 ]

 첫 번째 매개변수인 _Ms 는 연기되는 시간 또는 주기적인 시간으로 밀리 초를 받습니다.

 두 번째 매개변수인 _Value 는 전송할 message 입니다.

 세 번째 매개변수인 _PTarget 은 전송한 message 를 받을 대상 message block 입니다. 기본 값이 NULL 인데 NULL 인 경우에는 생성된 이후에 link_target() 을 이용하여 연결할 수 있습니다.

 여기서 주의해야 할 점은 timer 는 단 하나의 message block 만을 연결할 수 있다는 것입니다. link_target() 으로 여러 message block 들을 연결할 경우, 런타임 에러를 보게 될 것입니다.

 네 번째 매개변수인 message 의 전송을 연기 또는 주기적으로 전송하는 것을 선택합니다. true 일 경우 주기적으로 전송하며, false 는 연기 후, 단 한번만 전송됩니다.

 

void start()

 start() 는 timer 를 시작하는 함수로 생성된 후인 Initialized 또는 일시 정지된 Paused 상태에서만 동작합니다. 그리고 Started 상태가 됩니다.

 

void stop()

 stop() 은 timer 를 정지하는 함수입니다. 상태가 Stopped 가 되고, 해당 timer 를 다시 사용할 수 없습니다.

 

void pause()

 pause() 는 timer 를 일시 정지하는 함수입니다. 만약 주기적인 message 전송이 아니라 message 의 전송을 연기하도록 생성했다면, stop() 을 호출합니다. 그렇지 않을 경우, 내부 타이머를 멈추고, 상태를 Paused 로 변경합니다.

 

예제

 timer 의 특징을 알아보기 쉽도록 예제를 구현해보겠습니다.

 

시나리오

 채팅 중에 관리자에 의해서 사용자에게 전송되는 공지 사항을 시뮬레이션 해보도록 하겠습니다.

 

코드

#include <iostream>
#include <string>
#include <array>
#include <agents.h>

using namespace std;
using namespace Concurrency;

// 채팅 메시지 구조체
struct Message
{
	wstring		id;
	wstring		message;
};

// 채팅 메시지를 입력받는 agent.
class Typer
	: public agent
{
public:
	Typer( ITarget< Message >& buffer, const wstring id )
		: buffer( buffer )
		, isAvailable( true )
		, id( id ) { }

protected:
	void run()
	{
		while( this->isAvailable )
		{
			Message sendingMessage = { this->id };

			getline( wcin, sendingMessage.message );			

			send( this->buffer, sendingMessage );
		}		

		this->done();
	}

private:
	ITarget< Message >&		buffer;
	bool					isAvailable;
	wstring					id;
};

// 전달받은 메시지를 화면에 출력하는 agent.
class MessageDisplayer
	: public agent
{
public:
	MessageDisplayer( ISource< Message >& source )
		: source( source ) { }

protected:
	void run()
	{
		while( true )
		{
			Message receivedMessage = receive( this->source );

			wcout << L"[" << receivedMessage.id << L"]: " << receivedMessage.message << endl;
		}

		this->done();
	}

private:
	ISource< Message >&		source;
};

int main()
{
	// 시스템 로케일 정보로 설정.( 한글 깨짐 방지 )
	setlocale(LC_ALL, "");

	// 전달된 메시지를 보관하는 버퍼.
	unbounded_buffer< Message > messageBuffer;
	
	// 공지로 전달할 메시지.
	Message noticeMessage = { L"공지", L"2010년 8월 28일에 Visual Studio Camp 가 열립니다." };

	// 5초에 한 번씩 주기적으로 메시지를 전달할 timer
	timer< Message > notifier( 5000, noticeMessage, &messageBuffer, true );

	// timer 시작.
	notifier.start();

	// agent 들. 
	Typer typer( messageBuffer, L"MuMbi" );
	MessageDisplayer displayer( messageBuffer );

	array< agent*, 2 > agents = { &typer, &displayer };

	// agent 들 작업 시작.
	for_each( agents.begin(), agents.end(), []( agent* pAgent )
	{
		pAgent->start();
	} ) ;

	// agent 들 작업 종료까지 대기.
	agent::wait_for_all( agents.size(), &*agents.begin() );
}

[ 코드2. timer 를 이용한 주기적인 message 전송 예제 ]

 Typer 클래스는 사용자의 입력을 메시지 버퍼로 전송하는 역할을 하고, MessageDisplayer 클래스는 전송된 메시지들을 화면에 출력해주는 역할을 합니다.

 timer 는 5초마다 공지 사항을 메시지 버퍼로 전달합니다.

 결국 MessageDisplayer 클래스는 사용자의 메시지와 주기적으로 전달되는 메시지를 모두 화면에 출력하게 됩니다.

 

[ 그림1. timer 를 이용한 주기적인 message 전송 예제 결과 ]

[ 그림1. timer 를 이용한 주기적인 message 전송 예제 결과 ]

 

마치는 글

 이번 글을 마지막으로 AAL 에서 제공하는 모든 message block 들에 대해서 알아보았습니다.

 제공되는 message block 들은 각각 모두 사용할 상황이 다른 특징을 가지고 있고, 굉장히 유용합니다. 하지만 약간의 부족한 점들을 느끼고, 아쉬운 점이 보입니다.

 그런 점들을 보완하여 우리가 원하는 message block 을 만들 수 있습니다. 그렇게 만들어진 message block 은 스레드에 안전함을 보장해주어야 합니다. 

 다음글에서는 스레드 안전 보장을 위한 동기화 데이터 구조에 대해서 알아보도록 하겠습니다.

잊고 계셨을지도 모를 jqGrid 마지막편입니다.
이번 시간은 jqGrid를 이용하여 데이터를 추가, 편집, 삭제해보는 시간을 가져보도록 하겠습니다.


뷰페이지부터 보죠


지난 포스팅에 이어나갑니다. 먼저 가장 중요한 스크립트 부분을 보시면,

var updateDialog = {
                url: '<%= Url.Action("Update", "Home") %>'
                , closeAfterAdd: true
                , closeAfterEdit: true
                , modal: true
                , onclickSubmit: function (params) {
                    var ajaxData = {};
                    var list = $("#list");
                    var selectedRow = list.getGridParam("selrow");
                    rowData = list.getRowData(selectedRow);
                    ajaxData = { dirId: rowData.dirId };
                    return ajaxData;
                }
                , width: "400"
            };
            $.jgrid.nav.addtext = "추가";
            $.jgrid.nav.edittext = "편집";
            $.jgrid.nav.deltext = "삭제";
            $.jgrid.edit.addCaption = "전화번호부 추가";
            $.jgrid.edit.editCaption = "전화번호부 편집";
            $.jgrid.del.caption = "전화번호부 삭제";
            $.jgrid.del.msg = "정말 삭제하실거에요?";
            $("#list").jqGrid({
                url: '<%= Url.Action("EntityGridData", "Home") %>',
                datatype: 'json',
                mtype: 'POST',
                colNames: ['No', '이름', '전화번호', '이메일', '단축다이얼'],
                colModel: [
                  { name: 'dirId', index: 'dirId', width: 40, align: 'center', editable: true, editrules: { edithidden: false }, hidedlg: true, hidden: true },
                  { name: 'name', index: 'name', width: 200, align: 'left', editable: true, edittype: 'text', editrules: { required: true }, formoptions: { elmsuffix: ' *'} },
                  { name: 'phone', index: 'phone', width: 200, align: 'left', editable: true, edittype: 'text', editrules: { required: true }, formoptions: { elmsuffix: ' *'} },
                  { name: 'email', index: 'email', width: 300, align: 'left', editable: true, edittype: 'text', editrules: { required: true, email: true }, formoptions: { elmsuffix: ' *'} },
                  { name: 'speedDial', index: 'speedDial', width: 200, align: 'center', editable: true, edittype: 'text', editrules: { required: true }, formoptions: { elmsuffix: ' *'}}],
                pager: $('#pager'), 
                emptyrecords: "Nothing to display",             
                rowNum: 3,
                rowList: [3, 10, 20, 50],
                sortname: 'dirId',
                sortorder: "desc",
                viewrecords: true,
                caption: '전화번호부',
                ondblClickRow: function (rowid, iRow, iCol, e) {
                    $("#list").editGridRow(rowid, updateDialog);
                }
            }).navGrid('#pager',
                {
                    edit: true, add: true, del: true, search: false, refresh: true
                },
                updateDialog,
                updateDialog,
                updateDialog

            );

너무 많이 바뀌었나요? 그래도 알아보시죠? :) 굵은거~굵은거~

먼저 보이는것은 updateDialog가 보이네요. url도 보이고 submit 하고 ajax도 보이고.
데이터의 추가,편집,삭제시에 뜨는 팝업창에서 할일들이죠. Home 컨트롤러의 Update라는 액션메쏘드를 호출할거고요.
추가, 편집 후에는 창을 닫을 것이고(closeAfterAdd: true, closeAfterEdit: true), 모달창이고(modal: true), submit시 ajax를 사용하는 것 같아보이네요^^

다음으로 보이는게 $.jgrid.nav... 입니다. 이것은 지난 시간에도 말씀드렸던  grid.locale-en.js 을 수정하는 부분입니다. 이런 언어파일에는 디폴트값이 들어가 있다고 말씀드렸었죠? 기억하시죠? ;;

$.jgrid.edit = {
    addCaption: "Add Record",
    editCaption: "Edit Record",
    bSubmit: "Submit",
    bCancel: "Cancel",
    bClose: "Close",
    processData: "Processing...",
    msg: {
        required:"Field is required",
        number:"Please, enter valid number",
        minValue:"value must be greater than or equal to ",
        maxValue:"value must be less than or equal to",
        email: "is not a valid e-mail",
        integer: "Please, enter valid integer value",
        date: "Please, enter valid date value"
    }
};
$.jgrid.del = {
    caption: "Delete",
    msg: "Delete selected record(s)?",
    bSubmit: "Delete",
    bCancel: "Cancel",
    processData: "Processing..."
};
$.jgrid.nav = {
    edittext: " ",
    edittitle: "Edit selected row",
    addtext:" ",
    addtitle: "Add new row",
    deltext: " ",
    deltitle: "Delete selected row",
    searchtext: " ",
    searchtitle: "Find records",
    refreshtext: "",
    refreshtitle: "Reload Grid",
    alertcap: "Warning",
    alerttext: "Please, select row"
};

뷰페이지에서 수정한 부분은 두껍게 표시하였습니다.

$.jgrid.edit 의 msg중 required:"Field is required", email: "is not a valid e-mail" 이 부분이 뷰페이지의 colModel의 editrules: { required: true, email: true } 값과 매치가 되는 거죠.
또, grid의 네비게이션바에서 추가, 편집, 삭제 표시가 이미지로만 되어있었던 것을( $.jgrid.nav 의 add,edit,del 텍스트값이 빈값으로 되어있죠?) 뷰페이지에서 타이틀을 달아본겁니다.

나머지 부분도 재미있게 수정해서 테스트해보세요^^ 버튼 이름 변경, 필수값 에러 메시지, 경고메시지 등이 수정가능하네요. 



데이터를 처리하자


컨트롤러의 액션메쏘드가 추가되었겠죠? 다음은 HomeController의 추가된 Update 액션메쏘드입니다.

public ActionResult Update(TelDir telInfo, FormCollection formCollection)
{
    var operation = formCollection["oper"];
    if (operation.Equals("add"))
    {
       TelDir telData = new TelDir();
       telData.name = telInfo.name;
       telData.phone = telInfo.phone;
       telData.speedDial = telInfo.speedDial;
       telData.email = telInfo.email;
       _db.AddToTelDirSet(telData);
       _db.SaveChanges();
    }
    else if (operation.Equals("edit"))
    {
       var telData = _db.TelDirSet.First(m => m.dirId == telInfo.dirId);
       telData.name = telInfo.name;
       telData.phone = telInfo.phone;
       telData.speedDial = telInfo.speedDial;
       telData.email = telInfo.email;
       _db.SaveChanges();
    }
    else if (operation.Equals("del"))
    {
       var telData = _db.TelDirSet.First(m => m.dirId == telInfo.dirId);
       _db.DeleteObject(telData);
       _db.SaveChanges();
    }
    return Content("ok");
}

각 요청(add,edit,del) 대로 분기하여 처리하도록 하였습니다.



이제야 보는구나


실행을 해서 결과화면을 보면


멋드러지게 지난시간과는 다른 모습의 grid 가 있는 것을 확인할 수 있습니다. 추가,편집,삭제 버튼이 들어가 있고 옆에 리프레쉬 버튼도 있네요.

추가 버튼을 누르면


깔끔한 박스가 뜨네요. 전 슈퍼맨을 등록해보도록 하겠습니다. 값을 입력하지 않고 그냥 Submit 버튼을 살짝 눌러보시면 이름 입력하라고, 전화번호 입력하라고 아우성일겁니다. editrules: { required: true } 이것때문이죠. 매치된 메시지는 언어 스크립트에 있는 required:"Field is required" 이고요. 값을 정상적으로 입력후에 Submit을 하면...
바로바로 처리가 가능하네요^^ 모달창이 닫히는 부분은 위에서 설명드렸던 closeAfterAdd: true 때문인거죠. 편집하는 부분도 해볼까요? closeAfterEdit: truefalse로 하여 테스트해보세요. 모달창이 닫히지 않고 수정한 값들이 화면의 울렁거림 없이 처리가 되는 것을 확인하실 수 있습니다.

편집하는 부분을 테스트 하시려면 먼저 수정하려는 해당 로우(row)를 선택하신 후 편집 버튼을 클릭하시거나, 해당 로우를 더블클릭하시면 됩니다. 해보시죠?^^;


마지막으로 정말 삭제하실거에요? 를 본후 노는 시간을 마치도록 하겠습니다.




마무리요


정말 jquery 관련 플러그인들은 참 편리하네요. 이렇게 손쉽게 처리가 가능하니 말이죠.
암튼, 이번시간으로 jqGrid는 마치겠습니다. 뭐 한것도 없이 마친다고 하니 웃기네~ 라고 말씀을 해주셔도 마칠겁니다.^^;

아직은 무덥고, 다양한 날씨속에서 지내는 지금, 건강 꼭 챙기세요. 감사합니다^^

참고자료 : http://elijahmanor.com/webdevdotnet/post/jQuery-jqGrid-Plugin-Add-Edit-Delete-with-ASPNET-MVC.aspx

VS2010 C++ 프로젝트의 디렉토리 설정

Visual C++ 10 2010. 8. 9. 09:00 Posted by 알 수 없는 사용자

VS2008까지는 도구’ – ‘옵션메뉴를 통해서 VC++ 디렉토리를 설정하였습니다.

이렇게 설정한 정보는 모든 VC++ 프로젝트에 적용됩니다.

 

그러나 VS2010에서는 각 프로젝트 별로 VC++ 디렉토리를 설정합니다.

 


각 프로젝트 마다 독립적으로 설정을 할 수 있어서 편한 부분도 있지만 때로는 모든 프로젝트에 공통적으로 적용되어야 하는 경우는 매번 설정하는 것이 귀찮을 수 있습니다.

( 예로 DirectX boost 라이브러리 등 )

 

이런 경우 속성 매니저를 통해서 VC++ 디렉토리를 설정하면 모든 프로젝트에 공통적으로 적용할 수 있습니다.

 

1. 일단 아무 프로젝트 하나를 열어 놓습니다.

2. 메뉴의 보기’ -> ‘속성 관리자를 선택합니다.



3. 속성 관리자에서 ‘Microsoft.Cpp.Win32.user’를 더블 클릭해서 열어 놓습니다.


 

여기서 설정한 정보는 모든 프로젝트에 공통적으로 적용됩니다.

 

 

Asynchronous Agents Library
– message block 7. ( join & multitype_join )

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

 

시작하는 글

 이 글에서는 동기화 메커니즘으로 사용할 수 있는 joinmultitype_join 에 대해서 알아보겠습니다. join 은 이전 글에서 소개한 choice 와 함께 동기화 메커니즘으로 사용할 수 있습니다.

 

join< _Type, _JType >

 join 은 연결된 모든 message block 에서 message 를 받아올 때까지 기다리는 역할을 합니다. join 의 이 역할을 수행하기 위해서는 전달 함수인 receive() 와 함께 사용해야 합니다.

 템플릿 매개변수인 _Type 은 message 의 타입입니다. 연결된 message block 들이 처리하는 message 타입으로 같은 타입이어야 합니다.

 템플릿 매개변수인 _JType 은 join 의 타입입니다. join 은 2가지 종류가 있습니다. 하나는 greedy 이고, 다른 하나는 non_greedy 입니다.

 _JType 에는 greedy 또는 non_greedy 를 사용할 수 있으며, 기본 템플릿 인자로 non_greedy 가 지정되어 있습니다.

 join 타입은 다음과 같은 enum 형으로 정의되어 있습니다.

enum join_type { 
    greedy = 0,
    non_greedy = 1 
};

[ 코드1. join 타입 ]

 두 타입의 join 모두 연결된 message block 들에서 message 를 받아올 때까지 기다리는 기능은 같습니다. 그렇다면 다른 점에 대해서 알아보겠습니다.

 기본 인자로 지정된 non_greedy 타입은 연결된 message block 들 중 어떤 message block 이 먼저 message 를 받아올 수 있는 상태가 되더라도 연결된 모든 message block 들이 모두 message 를 받아올 수 있는 상태가 될 때까지 message block 에 받아올 수 있는 message 들을 유지합니다. 연결된 모든 message block 들에서 message 를 받아올 수 있을 때가 되면 한꺼번에 message 들을 받아옵니다.

 반면 greedy 타입은 연결된 message block 들 중 어떤 message block 이 먼저 message 를 받아올 수 있는 상태가 되면 그 message 를 미리 받아옵니다. 이 때 이 message block 이 unbounded_buffer 라면 이로 인해 join 이 받아간 message 가 제거됩니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수가 없습니다. 하지만 ISource 인터페이스를 재정의한 link_target() 을 사용할 수 있으며, 이 함수는 join 과 다른 message block 을 연결하는데 사용하게 됩니다.

 가장 기본적인 생성자의 매개변수는 연결할 message block 의 개수입니다. 이 개수보다 실제로 연결된 message block 의 개수가 많으면 런타임에 에러를 발생하고, 적으면 교착상태에 빠지게 됩니다. 생성자의 매개변수로 지정된 message block 의 개수가 연결되어 message 를 받을 때까지 대기하기 때문입니다.

 

특징

 message block 이 join 을 연결할 경우 몇 가지의 특징들이 있습니다.

  • 하나의 message block 이 여러 개의 join 에 연결될 경우, 먼저 연결된 join 에 message 를 보냅니다.
  • 연결된 join 들 중 greedy join 이 있을 경우 greedy join 에게 먼저 message 를 보냅니다.
  • 연결된 join 들 중 greedy join 이 여러 개일 경우, 먼저 연결된 greedy join 에 message 를 보냅니다.

 

 또한 어떤 mesage block 들은 join 과 함께 사용할 수 없습니다.

  • single_assignment 는 단 한번만 send() 가 적용되고 이 후의 send() 는 무시되기 때문에 joinsingle_assignment 으로부터 message 를 한번만 받을 수 있다. 두 번 이상은 받을 수 없기 때문에 무한대기하게 되어 교착상태에 빠진다.
  • transformerjoin 에 연결한 후, 연결한 transformer 로 부터 message 를 받아가면 에러를 발생한다.

 

multitype_join< _TupleType, _JType >

 multitype_joinjoin 과 같은 기능을 하지만 다른 종류의 message 들을 받을 수 있습니다. 그래서 템플릿 매개변수가 message 타입이 아닌 tuple 타입을 받습니다. tuple 에 사용할 수 있는 타입은 choice 에서 사용하는 tuple 의 특징과 같습니다.

 multitype_join 의 타입은 join 과 마찬가지로 greedynon_greedy 를 사용할 수 있습니다.

 

멤버 함수

생성자와 소멸자를 제외한 public 인 멤버 함수는 없고, 인터페이스를 재정의한 멤버 함수들이 있습니다.

 

헬퍼 함수

 tuple 을 사용하는 choice 처럼 multitype_join 도 tuple 을 사용하므로 헬퍼 함수 없이는 굉장히 길고 복잡한 선언이 필요합니다.

그래서 다음과 같은 헬퍼 함수를 제공합니다.

unbounded_buffer< int > i;
unbounded_buffer< float > f;
unbounded_buffer< bool > b;

auto j = make_join( &i, &f, &b );

[ 코드2. 헬퍼 함수 make_join ]

 

예제

 join 의 특징을 살펴볼 수 있는 간략한 예제를 보겠습니다.

 

시나리오

 보통 온라인 게임에서 게임을 시작하게 되면 연결된 모든 클라이언트가 모두 로딩이 완료된 후 동시에 게임을 시작하게 됩니다. 그것을 join 을 이용해서 시뮬레이션 해보겠습니다.

 

코드

#include <iostream>
#include <array>
#include <random>
#include <agents.h>

using namespace std;
using namespace Concurrency;

const unsigned int CLIENT_COUNT = 10;

class Loader
	: public agent
{
public:
	~Loader()
	{
		this->isComplete.unlink_targets();
	}

	Loader( unsigned long id, join< bool >& synchronizer )
		: random_generator( id )
	{
		this->isComplete.link_target( &synchronizer );
	}

protected:
	void run()
	{
		this->Loading();

		send( this->isComplete, true );
		wcout << L"Load is complete." << endl;
		
		this->done();
	}

private:
	void Loading()
	{
		unsigned int loadingTime = random_generator() % 3000 + 1000;
		Concurrency::wait( loadingTime );
		wcout << L"Loading time: " << loadingTime / 1000.0f << L" sec..";
	}

private:	
	single_assignment< bool >	isComplete;
	mt19937						random_generator;
};

int main()
{	
	join< bool > synchronizer( CLIENT_COUNT );
	
	array< Loader, CLIENT_COUNT > loaderArray = {
		Loader( 1, synchronizer ),
		Loader( 2, synchronizer ),
		Loader( 3, synchronizer ),
		Loader( 4, synchronizer ),
		Loader( 5, synchronizer ),
		Loader( 6, synchronizer ),
		Loader( 7, synchronizer ),
		Loader( 8, synchronizer ),
		Loader( 9, synchronizer ),
		Loader( 10, synchronizer ),
	};

	for_each( loaderArray.begin(), loaderArray.end(), []( Loader& loader ) { loader.start(); } );

	receive( synchronizer );

	wcout << L"Game Start!" << endl;

	for_each( loaderArray.begin(), loaderArray.end(), []( Loader& loader ) { agent::wait( &loader ); } );
}

[ 코드3. join 을 이용한 동기화 예제 ]

 10개의 클라이언트 역할을 하는 agent가 있고, 각 클라이언트마다 로딩 속도가 다른 것을 시뮬레이션 하기 위해 난수를 사용하여 로딩 시간을 결정하였습니다.

 각 agent 는 완료를 나타내는 single_assignment 를 하나의 join 에 연결하여 로딩이 모두 완료될 때까지 기다립니다.

  join 에서 message 를 받는다는 것이 동기화가 완료된 것이므로 바로 게임을 시작합니다.

 

[ 그림1. join 을 이용한 동기화 예제 ]

[ 그림1. join 을 이용한 동기화 예제 ]

 

마치는 글

 이번 글에서 joinmultitype_join 에 대해서 알아보았습니다.

 choice 와 비교해서 choice 는 연결된 message block 들 중 먼저 들어온 message 가 있으면 바로 message 를 받을 수 있던 반면에 join 은 연결된 message block 들로부터 message 를 모두 받을 때까지 대기합니다.

 이 message block 들, choicejoin, multitype_join 은 Win32 API 의 WaitForMultipleObjects() 와 비슷한 기능을 한다는 것을 알 수 있습니다.

 이 message block 들의 특징은 외부 이벤트 객체가 아닌 데이터 자체가 이벤트로 바인딩되어 처리된다는 점입니다.

결론적으로 이 message block 들을 이용해 동기화를 구현할 수 있음을 알 수 있습니다.

Asynchronous Agents Library
– message block 6. ( choice )

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

 

시작하는 글

 unbounded_buffer, overwrite_buffer 그리고  single_assignment 와 같이 message 를 보관하는 버퍼 기능의 message block 들과, call, transformer 와 같이 지정된 함수형 객체를 수행하는 기능의 message block 들에 대해서 알아보았습니다.

 이번 글에서는 message block 들의 상태를 기반으로 동작하는 message block 들 중 choice 에 대해서 알아보도록 하겠습니다.

 

choice< _TupleType >

 choice 의 특징은 지정된 message block 들 중 가장 먼저 messge 를 가져올 수 있는 message block 을 가리키는 기능입니다. 다시 설명하면 어떤 message 를 받을 준비가 된 message block 들을 choice 에 지정한 후, 지정된 message block 들 중 가장 먼저 message 를 받은 message block 을 choice 가 가리키게 됩니다.

 

tuple 의 조건

 choice 은 message 를 받을 준비가 된 message block 들을 지정할 때 tr1 의 tuple 을 사용합니다. 이 글의 주제가 tr1 이 아닌 만큼 tuple 에 대한 자세한 설명은 생략하겠습니다. 간략히 설명하면 std::pair 의 일반화된 객체라고 생각하시면 됩니다. std::pair 가 2개의 타입을 저장할 수 있다면 tuple 은 10개의 여러 가지 타입을 저장할 수 있습니다.

 그래서 다른 message block 과 달리 템플릿 매개변수가 message 타입이 아닌 여러 message block 들의 타입을 저장할 수 있는 tuple 의 타입입니다.

 하지만 아무 tuple 이나 사용할 수 있는 것이 아닙니다. 약간의 조건을 충족시키는 tuple 이어야만 합니다.

  • tuple 의 구성 요소로 source_type 이라는 typedef 를 가지고 있는 message block 들이어야 한다. ( message block 중 call 은 source_type 이 정의되어 있지 않기 때문에 사용할 수 없습니다. )
  • message block 들은 복사 생성자와 배정 연산자( = ) 의 사용을 금지( private )하고 있으므로 tuple 의 구성 요소로 포인터 타입을 사용해야 한다.

 

특징

 choice 는 여러 message block 들 중 가장 먼저 message 를 받은 message block 을 가리킵니다. 그 말은 choice 는 tuple 로 지정된 message block 들의 index 를 가지고 있다는 말입니다.

 그런데 그 index 를 저장하기 위한 변수로 내부적으로 single_assignment 를 사용합니다. 그 뜻은 single_assignment 는 단 한 번만 값을 저장할 수 있기 때문에 choice 는 가리키게 된 message block 의 index 를 변경할 수 없습니다. 즉, 일회성이라는 말이 됩니다.

 receive() 로 choice 의 message 를 받아오면 지정된 message block 들 중 하나라도 message 를 받을 때까지 기다리다가 message 를 받은 message block 이 생기면 그 message block 의 index 를 반환합니다.

 위의 특징을 응용하면 여러 message block 들 중 하나라도 message 를 받을 때까지 대기하는 기능으로 사용할 수 있습니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수 중 인터페이스로 재정의된 함수들을 제외한 함수들입니다.

인터페이스를 재정의한 멤버 함수들은 이전 글인 2010/07/10 - [Language Development/VC++ 10 Concurrency Runtime] - Asynchronous Agents Library – message block 1. ( 인터페이스 ) 를 참고하시기 바랍니다. 

 

bool has_value() const

 현재 message block 의 index 를 가지고 있는지 반환합니다. 내부적으로 single_assignmenthas_value() 를 호출합니다.

 

size_t index()

 가지고 있는 message block 의 index 를 반환합니다. 내부적으로 single_assignmentvalue() 를 호출하고 하고, 그 value() 는 receive() 를 사용하기 때문에 아직 index 를 가지고 있지 않다면 가질 때까지 대기하게 됩니다.

 

template <typename _Payload_type>
_Payload_type const & value()

 choice 가 가리키고 있는 index 의 message block 의 message 의 값을 반환합니다. message 의 타입을 명시적으로 호출해야 하기 때문에 사용하기 불편합니다.

 

헬퍼 함수

 choice 는 tuple 을 사용하기 때문에 선언이 복잡할 수 밖에 없습니다. 그래서 조금 더 쉽게 선언을 할 수 있도록 헬퍼 함수를 제공합니다.

unbounded_buffer< int > i;
unbounded_buffer< float > f;
unbounded_buffer< bool > b;

tuple< unbounded_buffer< int >*, unbounded_buffer< float >*, unbounded_buffer< bool >* > tp( &i, &f, &b );
choice< tuple< unbounded_buffer< int >*, unbounded_buffer< float >*, unbounded_buffer< bool >* > > ch( tp );

[ 코드1. 헬퍼 함수를 사용하지 않은 choice 선언 ]

 헬퍼 함수를 사용하지 않으면 템플릿 매개변수인 타입들을 모두 적어야 하기 때문에 굉장히 길어집니다. 특히나 message block 들의 타입이 굉장히 길기 때문에 더욱 불편합니다. typedef 를 사용하더라도 길기는 마찬가지 입니다.

 

unbounded_buffer< int > i;
unbounded_buffer< float > f;
unbounded_buffer< bool > b;

auto ch = make_choice( &i, &f, &b );

[ 코드2. 헬퍼 함수를 사용한 choice 선언 ]

 헬퍼 함수와 Visual studio 2010 에서 지원하는 C++0x 문법인 auto 를 사용하여 더욱 더 짧게 선언할 수 있습니다.

 

예제

 choice 의 특징을 쉽게 알아볼 수 있는 예제를 구현해보겠습니다.

 

시나리오

 두 agent 를 사용해서 어떤 agent 가 더 빨리 수행되는가를 육상에 비유하여 표현해보았습니다. choice 객체는 두 agent 중 빨리 수행한 쪽을 판별하는 역할을 합니다.

 

코드

#include <iostream>
#include <string>
#include <agents.h>

using namespace std;
using namespace Concurrency;

class Runner
	: public agent
{
public:
	Runner( ITarget< bool >& target, const wstring& name )
		: target( target )
		, name( name ) { }

	const wstring& GetName() const
	{
		return this->name;
	}

protected:
	void run()
	{
		for( unsigned int i = 0; i < 1000; ++i )
		{
			if( 0 == i % 100 )
				wcout << this->name << L" - " << i << L"m." << endl;
		}

		wcout << this->name << L" - " << L"Finished!" << endl;

		asend( target, true );

		this->done();
	}

private:
	ITarget< bool >&	target;
	wstring				name;
};

int main()
{
	single_assignment< bool > isFinished[2];

	auto winnerRecoder = make_choice( &isFinished[0], &isFinished[1] );

	Runner runner[2] = { Runner( isFinished[0], L"Carl Lewis" ), Runner( isFinished[1], L"Usain Bolt" ) };
	runner[0].start();
	runner[1].start();

	agent* agents[2] = { &runner[0], &runner[1] };
	agent::wait_for_all( 2, agents );

	wcout << runner[ receive( winnerRecoder ) ].GetName() << L" is winner!" << endl;
}

[ 코드3. choice 를 사용한 먼저 수행된 agent 판별 예제 ]

 두 agent 에 육상 선수인 칼루이스와 우사인 볼트의 이름을 붙여보았습니다. 각 agent 는 수행이 완료되면 완료 message 를 single_assignment 에 보내고, choice 객체는 완료 message 가 먼저 도착한 message block 의 index 를 갖게 됩니다.

 그 index 를 이용해 해당 agent 의 육상 선수 이름을 출력합니다.

 이 예제의 결과는 스케쥴링에 따라 다르기 때문에 실행할 때마다 결과가 다를 수 있습니다.

 

[ 그림1. choice 를 사용한 먼저 수행된 agent 판별 예제 ]

[ 그림1. choice 를 사용한 먼저 수행된 agent 판별 예제 ]

 

마치는 글

 이번에는 tuple 을 이용하는 choice 에 대해서 알아보았습니다. Visual studio 2010 에서 지원하는 C++0x 문법을 사용하면 좀 더 쉽게 사용할 수 있습니다.

다음 글 또한 새로운 message block 에 대해서 알아보도록 하겠습니다.

WPF 리본 컨트롤 RTW 출시

WPF 2010. 8. 5. 12:00 Posted by 알 수 없는 사용자

오피스 2007 부터 적용되어온 리본 UI에 대해서는 이제 모르는 분이 없을 정도로 많이 대중화 되었습니다.

윈도우 7에서는 그림판, 워드패드 등의 기본 프로그램에도 리본 UI가 적용되어서 사용자에게 편리함을 주고 있습니다.

이러한 리본 UI를 적용해서 개발을 하고 싶을 때는 MFC를 이용한 방법이 있습니다.

  

MFC 프로젝트를 생성하고 마법사에서 오피스 스타일의 프로젝트를 선택하면 리본 UI가 적용되어 사용 할 수 있습니다.

이렇게 프로젝트를 생성하고 실행시켜보면 리본 UI가 적용된 것을 확인 할 수 있습니다.

MFC에서는 이렇게 기본적으로 지원을 해주었지만 또 다른 윈도우 어플리케이션 개발 방법인 WPF에서는 지금까지 정식으로 지원을 하지 못했습니다.

기본 컨트롤에서는 제공하지 못하고 따로 컨트롤이 제작되어 지원을 준비하고 있었는데 이번에 RTW 버전이 출시되어 WPF 기반의 응용 프로그램 개발에서 리본 UI를 쉽게 사용할 수 있게 되었습니다.

RTW 출시 이전 단계에서는 .dll 파일을 수동으로 참조해서 번거롭게 작업을 해야 했었는데 이번 RTW부터 많은 편의성이 향상되었습니다.

첫째,  .MSI 인스톨러를 지원하여 번거로운 작업을 하지 않아도 쉬운 설치로 사용 할 수 있게 개선되었습니다.

둘째, WPF 개발에 많이 사용되는 MVVM 패턴을 지원하여 최신의 개발 방법을 지원하고 있습니다.

셋째, WPF 개발을 할 때 Expression Blend 사용이 거의 필수적인데 Blend에서 디자인 타임에서 리본 UI를 지원하여 편리한 UI 개발을 할 수 있게 되었습니다.

넷째, Visual Studio와 Expression Blend에서 프로젝트 템플릿을 지원하여 리본 UI 스타일 개발을 쉽고 빠르게 해줍니다.

 

   1: <ribbon:RibbonWindow x:Class="WpfRibbonApplication1.MainWindow"
   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:         xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
   5:         Title="MainWindow"
   6:         x:Name="RibbonWindow"
   7:         Width="640" Height="480">

프로젝트를 생성하고 xaml 코드를 살펴보면 ribbon 네임스페이스에 관련 컨트롤이 추가된 것을 볼 수 있습니다.

리본 UI 컨트롤은 ribbon 접두어를 사용하면 해당 컨트롤이 표시되게 됩니다.

그리고 일반 WPF 프로젝트는 루트 엘리먼트가 Window인데 리본 UI가 적용되면 RibbonWindow가 사용되는 것을 알 수 있습니다.

   1: <ribbon:Ribbon x:Name="Ribbon">
   2:     <ribbon:Ribbon.ApplicationMenu>
   3:         <ribbon:RibbonApplicationMenu SmallImageSource="Images\SmallIcon.png">
   4:             <ribbon:RibbonApplicationMenuItem Header="Hello _Ribbon"
   5:                                               x:Name="MenuItem1"
   6:                                               ImageSource="Images\LargeIcon.png"/>
   7:         </ribbon:RibbonApplicationMenu>
   8:     </ribbon:Ribbon.ApplicationMenu>
   9:     <ribbon:RibbonTab x:Name="HomeTab" 
  10:                       Header="Home">
  11:         <ribbon:RibbonGroup x:Name="Group1" 
  12:                             Header="Group1">
  13:             <ribbon:RibbonButton x:Name="Button1"
  14:                                  LargeImageSource="Images\LargeIcon.png"
  15:                                  Label="Button1" />
  16:  
  17:             <ribbon:RibbonButton x:Name="Button2"
  18:                                  SmallImageSource="Images\SmallIcon.png"
  19:                                  Label="Button2" /> 
  20:         </ribbon:RibbonGroup>
  21:         
  22:     </ribbon:RibbonTab>
  23: </ribbon:Ribbon> 

실제 UI부분의 코드를 보면 이전에는 일일이 직접 코딩을 해줘야 했던 부분이 자동적으로 가능하게 되어 정말 편리하게 변했습니다. 이제 리본 컨트롤을 사용하여 WPF 환경에서도 편리하게 UI 개발을 할 수 있게 되었습니다.

이번에는 간단한 소개 정도로 마치고 다음부터는 WPF에서 리본 컨트롤을 사용하는 세부적인 내용을 차근차근 알아가도록 하겠습니다.

※ WPF Ribbon 컨트롤 다운로드

Asynchronous Agents Library
– message block 5. ( transformer )

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

 

시작하는 글

 이번 글에서는 transformer 라는 message block 에 대해서 알아봅니다.

 transformer 는 지난 글에서 알아본 call 과 유사하나 좀 더 유연합니다. call 과 비교해서 보시고, calltransformer 를 언제 사용해야 할지 구분해 보시기 바랍니다.

 

transformer< _Input, _Output >

 transformercall 과 마찬가지로 지정된 함수, 함수 객체를 수행하는 역할을 합니다. 하지만 call 과는 확실히 다릅니다. 이름에서도 알 수 있듯이 message 를 변환하는 기능을 가지고 있습니다. 이 기능이 call 과 어떻게 다른지 알아보겠습니다.

 

특징

 transformer 의 특징 중 첫째는 결과를 반환한다는 것입니다. 변환하는 기능을 가지고 있기 때문에 변환된 값을 내보내 주어야 하기 때문입니다. 템플릿 매개변수를 보고 이미 눈치채신 분도 있겠지만, transformer 를 선언할 때 입력되는 매개변수의 타입과 반환 타입을 명시해주어야 합니다.

 이로 인해 내부적으로 call 과 다른 점은 반환 값을 보관하는 큐( queue ) 를 포함하고 있습니다. call 은 작업들을 순차적으로 처리하기 위한 작업 큐를 가지고 있고, transformer 는 작업 큐 뿐만 아니라 반환 값을 보관하는 큐도 가지고 있습니다.

 반환 값을 보관하는 큐의 값을 꺼내오기 위해서 receive() 나 try_receive() 를 사용할 수 있습니다. 이 특징은 message 들을 전달, 보관하는 unbounded_buffer 와 비슷합니다. 하지만 unbounded_buffer 처럼 사용하시면 안됩니다.

 그 이유는 transformer 는 두 가지 기능, 변환 함수를 수행하는 기능과 변환된 값을 내보내는 기능을 가지고 있는데 이 둘을 동시에 처리할 수 없기 때문입니다. 변환 함수를 수행하는 중인 transformer 에서 변환된 값( 반환 값 )을 얻어내려 시도해도 변환 함수를 수행 중일 때에는 변환된 값을 내어주지 않아 원하는 시기에 값을 얻어낼 수 없습니다.

 원하는 시기에 값을 제대로 얻기 위해서는 변환된 값을 unbounded_bufferoverwrite_buffer 등과 같이 message 를 보관할 수 있는 message block 에 전달한 후, 그 message block 에서 값을 얻어와야 합니다.

 두 번째 특징은 다른 message block 들과 연결하여 변환된 반환 값을 스스로 전달할 수 있다는 것입니다. ISource 인터페이스의 link_target() 을 사용하는데, 이 기능을 사용하여 절차적인 작업들을 잘게 나누어 순차적으로 처리할 수 있습니다. 이러한 메커니즘을 파이프라인( pipeline ) 이라고 합니다.

 일반적인 C++ 프로그램에서도 파이프라인 개념은 이미 많이 사용되고 있습니다.

 FunctionA( FunctionB( FunctionC() ) ) 와 같이 진행되는 처리도 파이프라인으로 볼 수 있습니다. 하지만 transformer 를 이용한 파이프라인은 이와 차이점이 있습니다. 바로 비 동기 처리가 가능하다는 것입니다.

 하나의 파이프라인의 작업이 끝날 때까지 대기하지 않기 때문에, 여러 파이프라인의 작업이 동시에 수행할 될 수 있습니다.

 잠시 후, 이런 파이프라인을 이용하는 예제를 알아보도록 하겠습니다.

 

선언

template < class _Output, class _Input >
class transformer

[ 코드1. transformer 의 선언 ]

 transformercall 처럼 함수 타입을 지정할 수 없습니다.

 transformer 는 std::tr1::function<_Output(_Input const&)> 로 함수 타입이 고정되어 있습니다.

 

예제

 transformer 를 이용하여 파이프라인을 구성하여 처리하는 예제를 구현해보도록 하겠습니다.

 

시나리오

 문자열을 꾸미는 작업을 각 단계별로 나누어 파이프라인을 구성하고, 이것을 비 동기로 처리하는 시나리오입니다.

 

코드

#include <iostream>
#include <string>
#include <agents.h>

using namespace std;
using namespace Concurrency;

// 문자열을 꾸며주는 helper class.
class DecorationHelper
{
public:
	static wstring Boiler( wstring food )
	{		
		wcout << L"boiling.." << endl;
		Concurrency::wait( 1000 );		
		return L"boiled " + food;
	}

	static wstring AddSugar( wstring food )
	{		
		wcout << L"adding sugar.." << endl;
		Concurrency::wait( 1000 );		
		return L"sweet " + food;
	}

	static wstring PutOnPlate( wstring food )
	{
		wcout << L"putting on a plate.." << endl;
		Concurrency::wait( 1000 );		
		return food + L" on a plate";
	}

	static wstring WrapUp( wstring food )
	{
		wcout << L"Wrapping the food up.." << endl;
		Concurrency::wait( 1000 );		
		return food + L".";
	}
};

int main()
{
	// 문자열 꾸밈 함수들을 수행하는 transformer message block 들.
	transformer< wstring, wstring > boiler( &DecorationHelper::Boiler );
	transformer< wstring, wstring > addSugar( &DecorationHelper::AddSugar );
	transformer< wstring, wstring > putOnPlate( &DecorationHelper::PutOnPlate );
	transformer< wstring, wstring > wrapUp( &DecorationHelper::WrapUp );

	// 최종 변환된 message 를 저장하는 message block.
	unbounded_buffer< wstring > result;

	// 꾸며진 문자열들을 다른 꾸밈 함수에 연결하여 파이프라인을 구성.
	boiler.link_target( &addSugar );
	addSugar.link_target( &putOnPlate );
	putOnPlate.link_target( &wrapUp );
	wrapUp.link_target( &result );

	// 구성된 pipeline 에 message 전달.
	asend( boiler, wstring( L"soup" ) );
	asend( boiler, wstring( L"noodle" ) );
	asend( boiler, wstring( L"water" ) );	

	// 최종 변환된 message 를 받아서 출력.
	while( true )
	{
		wcout << L"completed food: " << receive( result ) << endl;		
	}
}

[ 코드2. transformer 를 이용하여 파이프라인을 구성하고 비 동기로 처리하는 예제 ]

 문자열을 꾸미는 함수들을 각각 transformer 에 지정한 후, ISource 인터페이스인 link_target() 을 이용하여 파이프라인을 구성합니다.

 구성된 파이프라인의 첫 message block 인 boiler 객체에 인자로 사용할 문자열을 message 로 보내면 파이프라인이 비 동기로 처리됩니다.

 파이프라인의 마지막은 unbounded_buffer 를 사용하여 변환된 최종 값을 저장하고, 저장된 값들을 꺼내서 화면에 출력합니다.

 

[ 그림1. transformer 를 이용하여 파이프라인을 구성하고 비 동기로 처리하는 예제 ]

[ 그림1. transformer 를 이용하여 파이프라인을 구성하고 비 동기로 처리하는 예제 ]


 

마치는 글

  transformer 를 이용해 파이프라인을 구성하고 비 동기로 처리하는 방법을 알아보았습니다.

 지난 글에서 소개해드린 call 과는 확실히 다르다는 것을 알게 되셨을 것입니다.

 이 파이프라인 개념은 많은 곳에 적용되고 있으므로 유용하게 사용하실 수 있을 것입니다.

[Upgrade to VC++ 10] _WIN32_WINNT 버전 문제

Visual C++ 10 2010. 8. 2. 09:00 Posted by 알 수 없는 사용자

VS.NET(VS2002)에서 MFC 프로젝트로 만들었던 프로그램을 VC++10 프로젝트로 변환하여 컴파일 했더니 에러가 발생하면서 아래의 경고가 나왔습니다.

 

C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include\atlcore.h(35):#error This file requires _WIN32_WINNT to be #defined at least to 0x0403. Value 0x0501 or higher is recommended.

 

에러 내용은 프로젝트에서 정의된 _WIN32_WINNT 버전이 0x403인데 atlcore.h 버전이 최소 0x0501 이상이 되어야 한다는 것입니다.

 

그래서 _WIN32_WINN 정의한 stdafx.h 파일을 열어보니

#define _WIN32_WINNT 0x0400

되어 있었더군요. 그래서 일단 이것을 최신이 좋다라는 생각에 아래와 같이 했습니다. ^^;;

#define _WIN32_WINNT 0x0600

 

그랬더니 이제는 아래와 같은 에러가 나오더군요. -_-;

c:\program files\microsoft visual studio 10.0\vc\atlmfc\include\afxcmn3.inl(29): error C2065: 'CCM_SETWINDOWTHEME' : 선언되지 않은 식별자입니다.

 

그래서 바로 구글링 들어갔습니다.

쉽게 저와 같은 에러가 나와서 질문을 올린 글을 찾았고 답변도 보았습니다.

문제 해결은 stdafx.h 파일에 정의된 버전의 숫자를 아래와 같이 하면 된다고 하더군요

// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef WINVER // Allow use of features specific to Windows 95 and Windows NT 4 or later.
#define WINVER 0x0501 // Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINNT // Allow use of features specific to Windows NT 4 or later.
#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later.
#define _WIN32_WINDOWS 0x0501 // Change this to the appropriate value to target Windows Me or later.
#endif

#ifndef _WIN32_IE // Allow use of features specific to IE 4.0 or later.
#define _WIN32_IE 0x0500 // Change this to the appropriate value to target IE 5.0 or later.
#endif

 

이렇게 하니 문제 없이 빌드가 성공 되었습니다.

 

주위에서 VC++의 새로운 버전이 나와도 쉽게 사용하지 못하는 경우가 오래 전에 만들었던 프로젝트를 포팅 할 수 없어서 이전 버전을 어쩔 수 없이 사용한다는 이야기를 종종 듣습니다.

그러나 저는 운이 좋아서인지 2002버전부터 순차적으로 새 버전의 VC++을 사용할 수 있어서 VC++6에서 VS2002로 넘어갈 때만 빌드 문제를 겪었습니다.

그래서 이런 포팅에 대한 문제는 잘 알지 못합니다. 이번에는 예전에 만들었던 코드를 C++0x 코드로 바꾸고 싶어서 오래 전에 만들었던 프로젝트를 VC++ 10로 포팅하면서 정말 정말 오랜만에 이런 문제를 겪어 보게 되고 해결 방법을 포스팅 할 수 있었습니다.

 

혹시 앞으로 또 이런 경우가 발생하면 바로 바로 공유하도록 하겠습니다.  





Asynchronous Agents Library – message block 4. ( call )

VC++ 10 Concurrency Runtime 2010. 7. 31. 17:00 Posted by 알 수 없는 사용자

Asynchronous Agents Library
– message block 4. ( call )

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

 

시작하는 글

 이번 글에서는 call 이라는 message block 에 대해서 알아보겠습니다. 이번에 알아볼 call 또한 이미 알아본 message block 들과는 다른 특징이 있으니 특징을 이해하고, 필요할 때 바로 사용할 수 있어야 합니다.

 

call< _Type >

 call 은 함수 포인터와 같은 역할을 합니다. call 에 message 를 보내면 그 message 는 생성자에서 지정한 함수 포인터의 인자로 사용됩니다. 그래서 call 에 message 를 보낸다는 것은 함수를 호출하는 것과 같은 효과를 얻을 수 있습니다.

 

특징

 call 과 함수 호출의 다른 점이 있습니다. call 에 의해서 수행되는 함수를 동기 또는 비 동기로 수행되도록 선택할 수 있습니다.

 동기 전달 함수인 send() 를 사용하여 message 를 전달하면 지정된 함수의 수행이 종료될 때까지 기다립니다. 그렇기 때문에 지정된 함수의 수행이 종료되어야 해당 context 가 진행됩니다.

 반면에 비 동기 전달 함수인 asend() 를 사용하여 message 를 전달하면 지정된 함수의 수행이 종료될 때까지 기다리지 않고 호출한 context 는 계속 진행됩니다. 즉, 호출한 스레드와 call 의 함수가 동시에 진행됩니다.

 이처럼 동기와 비 동기 처리를 적절히 선택하여 구현할 수 있습니다.

 또 하나 기억해 두어야 할 특징은 하나의 call 객체에 message 를 여러 차례 보내더라도 동시에 수행되지 않습니다. 하나의 call 객체는 내부적으로 작업 큐( queue ) 를 가지고 있어 순차적으로 수행됩니다.

 하지만 하나의 call 객체가 아니라 서로 다른 call 객체는 asend() 를 이용해 동시에 수행될 수 있습니다. 서로 각각의 작업 큐를 가지고 있기 때문입니다.

 call 의 안타까운 점은 함수의 반환을 처리하지 못한다는 점입니다. 만약 지정된 함수의 결과를 얻어내야 한다면 해당 함수 내부에서 다른 message block 으로 결과를 message 로 보내야 합니다.

 call 은 생성자 이외에 public 인 멤버 함수가 없습니다.

 

선언

 사실 call 이라는 message block 의 타입은

template < class _Type, class _FunctorType = std::tr1::function< void( _Type const & ) > >
class call

[ 코드1. call 의 선언 ]

입니다.

 tr1 의 function 은 일반 함수 포인터와 멤버 함수 포인터, 함수 객체를 모두 가질 수 있으므로 기본 템플릿 매개변수인 _FunctorType 을 지정하지 않고 사용하는 것이 좋습니다. 

 위의 선언이 보여주듯이 _Type 은 수행될 함수의 매개변수의 타입입니다. bind 와 같은 어댑터 함수들과 함께 사용하여 여러 인자들을 바인딩할 수 있습니다.

 

예제

 call 의 특징들을 쉽게 알아볼 수 있는 예제를 구현해보았습니다.

 

시나리오

 Loader 라는 agent 클래스가 작업을 진행하고 call 을 이용해 진행 상황을 화면에 출력해주는 예제입니다.

 

코드

#include <iostream>
#include <agents.h>

using namespace std;
using namespace Concurrency;

class Loader
	: public agent
{
public:
	Loader( call< unsigned int >& displayDelegate )
		: displayDelegate( displayDelegate ) { }

protected:
	void run()
	{
		for( unsigned int percent = 0; percent <= 100; percent += 10 )
		{
			Concurrency::wait( 1000 );
			asend( this->displayDelegate, percent );
		}

		this->done();
	}

private:
	call< unsigned int >&		displayDelegate;
};

int main()
{
	call< unsigned int >	showLoading( []( unsigned int percent )
	{
		switch( percent )
		{
		case 0:		wcout << L"loading..";					break;
		case 100:	wcout << endl << L"complete!" << endl;	break;
		default:	wcout << L"..";							break;
		}		
	} );

	Loader loader( showLoading );

	loader.start();

	agent::wait( &loader );
}

[ 코드2. call 의 비 동기 처리를 이용한 예제 ]

 Loader agent 의 작업 진행 상황을 화면에 출력합니다. 진행률이 0 일 때는 “loading.." 을 출력하고, 이후, 진행이 완료되기 전까지 “..” 을 추가하여 진행 상황을 알리고, 완료되면 “complete!” 를 출력하여, 작업의 완료를 알립니다.

 

[ 그림1. call 의 비 동기 처리를 이용한 예제 ]

[ 그림1. call 의 비 동기 처리를 이용한 예제 ]

 

마치는 글

 이미 알아본 message block 들과는 많이 다른 call 에 대해서 알아보았습니다. call 은 함수의 수행을 동기 및 비 동기 처리로 할 수 있도록 도와주는 반면 결과를 얻기는 쉽지 않습니다.

 이러한 call 의 단점의 보완과 더 많은 특징을 가지고 있는 transformer 라는 message block 을 제공합니다.

 다음 글에서는 transformer 를 이용해 더욱 더 신나는 멀티 코어 프로그래밍을 해보도록 해보겠습니다.

[세미나] Visual Studio Camp #1

VSTS 2010 팀 블로그 2010. 7. 28. 15:26 Posted by POWERUMC
 

저희 "한국 Visual Studio 공식 " 에서 세미나를 주최합니다.

 

저희 "한국 Visual Studio 공식 " 온라인 블로그(http://vsts2010.net) 통해 다양한 분야의 전문가들이 활동하고 있습니다. 그리고 동안 저희 팀원들은 다양한 세미나 경험을 바탕으로 많은 강사진을 구축하였습니다. 경험을 바탕으로 저희 팀에서 주최하는 세미나를 진행하게 되었습니다.

 

다가오는 2010/08/28일에 Visual Studio Camp #1 진행하오니 많은 성원 부탁 드립니다.

 

 

 

세미나 등록은 아래의 링크를 통해 신청할 있습니다.

http://onoffmix.com/event/1676

 

 

 


Visual Studio Camp #1

- 주최 : 한국 Visual Studio 공식 팀
- 일시 : 2010년 8 28 토요일 오후 1:30~5
- 장소 : 타임 교육 센터
- 참가비 : 무료


세미나 아젠다

 

Native 트랙

.NET 트랙

Enterprise 트랙

14:00 ~ 14:50

Visual Studio 2010 : C++0x와 Windows 7

 

 

 

최성기

그것이 알고싶다 - C# 4.0의 변화, 그 진실은 무엇인가. 희망인가? 또 다른 혼란인가?

 

강보람 C# MVP

VS Team Foundation Server 2010 의 새로운 변화

 

 

김병진 Team System MVP

15:00 ~ 15:50

비주얼 스튜디오 2010 의 Concurrency Runtime 을 이용한 멀티 코어 제대로 활용하기

 

임준환

좋은 프레임워크 있으면 소개시켜줘 - ASP.NET MVC

 

 

 

박세식

소프트웨어 품질 향상을 위한 다양한 테스트 기법

 

 

 

엄준일 Team System MVP

16:00 ~ 16:50

DirectX11 을 기다리며...

 

 

조진현

Beginnig WCF

 

 

오태겸

SharePoint 2010 Enterprise 솔루션 개발

 

정홍주 SQL Server MVP

 




발표 내용 소개
Native 트랙 Visual Studio 2010 : C++0x와 Windows 7
동안 .NET 영역으로 적잖이 편중되었던 Visual Studio의 버전업에 비해 이번 2010 버전에서는 Native Code 개발환경에서도 많은 변화가 찾아왔다. C++0x 표준 반영에 의한 문법의 변화, 새로운 라이브러리 제공(Concurrency Runtime Library), Windows 7의 최신 기능들을 제어하기 위한 SDK의 업데이트 등이 그것이다. 본 세션을 통해 C++의 문법적인 변화와 Windows 7 기능 구현을 위한 SDK의 업데이트 사항들을 정리해본다.

비주얼 스튜디오 2010 의 Concurrency Runtime 을 이용한 멀티 코어 제대로 활용하기
요즘 가정의 PC 에 멀티 코어 프로세서가 많이 보급되어 있습니다. 하지만 실제로 PC 에 설치된 코어들을 모두 사용하는 애플리케이션들은 많지 않습니다. 이렇게 낭비되는 자원을 C++ 개발자가 쉽게 사용할 수 있도록 도와주는 Concurrency Runtime 을 비주얼 스튜디오 2010에서 제공합니다. 이 Concurrency Runtime 을 어떻게 시작해야 할지 알아보겠습니다.

DirectX11 을 기다리며...
조금씩 정보가 공개되면서 많은 변화를 예고하고 있는 DirectX11 에 대해서 살펴 볼 것입니다. 특히나 Tessellation, DirectCompute, Multi-threading 을 위한 기본 개념과 작업들에 대해서 체크해 볼 것입니다.
.NET 트랙 그것이 알고싶다 - C# 4.0의 변화, 그 진실은 무엇인가. 희망인가? 또 다른 혼란인가?
PDC 2008에 울려 퍼진 C# 4.0의 소식. 그 소식을 듣고 많은 사람들은 기대와 혼란을 가지게 되었다. C#은 분명히 정적 언어인데, 동적 언어에나 있을 법한 기능을 추가한다니? 이제 와서 뒷북일 수도 있는 C# 4.0의 변화에 대한 진실, 그 마지막 시리즈가 이제 시작된다. :)

좋은 프레임워크 있으면 소개시켜줘 - ASP.NET MVC
동안 아주 미묘하게 아쉬웠던 ASP.NET. 가려운 곳을 긁어줄 대안의 프레임워크가 나타났다. 웹 개발자들 한테 참~ 좋은데, 웹 개발자들 한테 정말 좋은데, 이걸 말로 그냥 할 수 없어서, 이번 기회에 소개한다.

Beginnig WCF
WCF는 서비스 지향 프로그래밍을 위해 마이크로소프트에서 개발 및 지원하는 기반 기술이며, 기존의 .NET 웹 서비스에 비해 유연성과 확장성이 뛰어나 최근 많은 관심을 받고 있습니다. 본 세션에서는 WCF가 무엇인지? 어떤 장점이 있는지? 그리고, WCF 를 이용하기 위해선 무엇이 필요한지? 에 대해 함께 알아보고, 마지막으로, WCF의 활용 예를 알아보도록 하겠습니다.
Enterprise 트랙 VS Team Foundation Server 2010 의 새로운 변화
Visual Studio Team Foundation Server 2010의 혁신적인 변화와 개선 부분, 프로젝트 및 형상관리와 Agile의 Scrum 을 이용한 방법론을 알아보고, 단지 소스 체크인/아웃만 하는 Visual Source Safe에서 업그레이드 하는 방법에 대하여 알아봅니다.

소프트웨어 품질 향상을 위한 다양한 테스트 기법
소프트웨어는 개발 및 릴리즈 과정까지 수 많은 과정을 겪는데, 소프트웨어가 점진적으로 진화함에 따라 결함의 발생률이 증가합니다. 이를 개선하기 위한 테스트 기법 단위 테스트, WhiteBox 테스트, 화면 테스트, 성능 테스트, 부하 테스트 다양한 테스트 기법을 알아봅니다.

SharePoint 2010 Enterprise 솔루션 개발
SharePoint 2010은 기업 협업 플랫폼으로 개발자들은 VS 2010을 이용하여 더 생산성 있고 효과적인 SharePoint 2010 개발을 진행할 수 있습니다. 본 세션에서는 SharePoint 2010 개발에 대한 가장 필요한 내용을 구체적으로 알아보며 이를 통해 가장 많은 요구사항에 대한 실무 솔루션을 구성하는 방법에 대한 내용을 알아보겠습니다.



발표자 소개
Native 트랙 최성기 / Visual Studio 공식 팀
엔씨소프트에서 온라인 게임 서버를 개발하고 있으며, 비주얼 스튜디오 2010 공식 팀 블로그 (http://vsts2010.net) 에서 MFC와 윈도우7 카테고리를 맡아 스터디를 하고 있다. 최근 UX 시장의 핫이슈인 ‘멀티터치’에 대해 많은 관심을 갖고 있다.
임준환 / Visual Studio 공식 팀
Visual Studio 2010 공식 팀 블로그( http://vsts2010.net ) 에서 C++, 게임 관련 필자로 활동하고 있다.
조진현 / Visual Studio 공식 팀
현재
 클라이언트 게임 프로그래머로써 재직 중입니다. Visual Studio 2010 공식 블로그(http://vsts2010.net에서 DirectX11 부분에서 활동 중입니다.
.NET 트랙 보람 / Visual Studio 공식 팀 시삽 / Microsoft C# MVP
Visaul Studio 공식
팀의 닷넷 파트 시삽을 맡고 있으며, Visual C# MVP이다. MSDN 주간 세미나, Techdays 2009, 2010 Spring, REMIX 10에 참여했으며, '그것이 알고싶다'를 2004년 부터 거의 빼놓지 않고 다 본 경력의 소유자이다. 개인 블로그 '워너비의 소프트웨어 팩토리'(http://blog.naver.com/netscout82)를 운영 중이며, 프로그래밍과 전혀 상관없는 이야기를 쓰고 있다.
박세식 / Visual Studio 공식 팀
아직까지는 꿈
많은 유부남 청년이다. 아이가 생기면 시간이 없다는 말에 몸서리 치면서 노력 중이다. Visual Studio 공식 팀 블로그에서 ASP.NET MVC 관련 포스팅을 하고 있고, 개인 블로그 sses's blog(http://sses.tistory.com)를 운영 중이다.
오태겸 / Visual Studio 공식 팀
오태겸, 현재 Hostway 에서 근무하고 있으며, 개인 블로그(
http://ruaa.tistory.com)와 Visual Studio 2010 공식 팀 블로그(http://vsts2010.net)에서 WCF 카테고리를 통해 있는 지식, 없는 지식 총 동원해가며, WCF에 관한 포스팅을 하고 있다.
Enterprise 트랙 김병진 / Visual Studio 공식 팀 시삽 / Microsoft Team System MVP / MCT
김병진 MCT/Microsoft MVP로 Visual Studio 2010 팀 블로그(
http://vsts2010.net)에서 활동하고 있으며, ALM 교육과 컨설팅을 통해 Microsoft 의 기술과 플랫폼기반의 개발과 설계 관련하여 강의과 컨설팅을 하고 있으며, 우리나라 소프트웨어 공학의 발전을 위해 열심히 노력하고 있습니다.
엄준일 / Visual Studio 공식 팀 대표 시삽 / Microsoft Team System MVP
엄준일 Microsoft Team System MVP 로 활동 하고 있으며, 개인 블로그(http://blog.powerumc.kr) 와 트위터(@powerumc) 를 통해 .NET 기술을 전파하고 있다. 그리고 Visual Studio 2010 공식 팀 블로그(http://vsts2010.net) 의 대표 시삽으로 팀 블로그와 트위터(@vsts2010) 를 운영하고 있다.
정홍주 / Visual Studio 공식 팀 / Microsoft SQL Server MVP
웹타임 교육센터에서 SQL, .NET 강의와 .NET, SharePoint 컨설팅을 하고 있다.
Microsoft SQL Server MVP 로 활동 하고 있으며 데브피아의 SQL Server 2005 시샵이다. SharePoint 2010 책을 집필하고 SharePoint 2010 관련 동영상과 미니클립을 서비스하고 있으며 현재 Visual Studio 2010 공식 팀 블로그(http://vsts2010.net) 에서 SharePoint 2010 관련 블로깅을 하고 있다. 향후 SharePoint 2010 개발 관련 여러 내용을 Open Source 할 예정이다.


오시는 길



경품 안내
Microsoft USB 키보드 3
Microsoft 무선 마우스 3
MSDN 1년 구독권 2개


후원
웹 타임 교육 센터

ASP.NET MVC 3 Preview 1 이 릴리즈 되었습니다.

ASP.NET MVC 2010. 7. 28. 15:00 Posted by 네버덜레스
아직 ASP.NET MVC 2 의 관련 글도 모두 정리하지 못하고 있는 저에게 ( 게을러서 죄송합니다 :-) )
ASP.NET MVC 3 프리뷰 1 이 릴리즈 되었다는 소식이 들어왔네요^^;
아흑 너무 빠르게 변화되는 참 좋은 세상~ ㅡ,.ㅡ;



ASP.NET MVC 3 소식 전파

ASP.NET MVC 3 프리뷰 1이 릴리즈 되었습니다. 여기서 다운 받으시면 됩니다.

일단 다운받아서 보니 MVC 3 관련된 것이 떡하니 템플릿으로 끄집어내져 있습니다.


정말이지.. 뭔가 많은 것들을 해봐야 할듯한 포스가 느껴집니다. 암튼.


뭐가 어떻게 된 것이냐?( 릴리즈에 추가된 사항들.. )



- 레이저 뷰엔진 :

ASP.NET MVC의 새로운 뷰엔진입니다. 코드를 최소하하도록 도와주죠^^ 자세한 것은 저희 팀블로그의 Razor in WebMatrix를 참고해주세요^^


- 다이나믹 뷰와 뷰모델 속성 :

딕셔너리를 다이나믹을 사용(기존 ViewData 객체를 더 간단한 문법으로 접근하게 해주죠^^)하여 컨트롤러와 뷰사이에 데이터를 전달합니다. 예를 들어, 기존의 경우,

ViewData["Title"] = "ASP.NET MVC 3가 웬말이냐?!";
ViewData["Message"] = "ASP.NET MVC 3 Preview 1이 릴리즈가 되었습니다.";

이렇게 ViewData 딕셔너리를 통해 뷰페이지에 전달하였습니다. 하지만 다이나믹 뷰모델 속성을 이용하여 다음과 같이 위의 두 값들을 ViewData 딕셔너리에 추가할 수 있습니다.

ViewData.Title = "ASP.NET MVC 3가 웬말이냐?!";
ViewData.Message = "ASP.NET MVC 3 Preview 1이 릴리즈가 되었습니다.";

뷰페이지에서도 다음과 같이 받습니다.

<h2>View.Title</h2>
<h2>View.Message</h2>


- 뷰 추가 다이얼로그 박스에서 뷰엔진을 선택할 수 있게 해줍니다. :


커스텀 뷰엔진을 포함한 이번에 추가된 Razor(CSHTML) 을 선택할 수도 있고~
ASPX(C#) 을 선택할 수도 있고~


- 글로벌 필터 :

모든 컨트롤들의 액션 메쏘드에 전역적으로 적용할 필터를 등록할 수 있다네요.
Global.asax의 Application_Start 메쏘드에서

GlobalFilters.Filters.Add(new MyActionFilter());

와 같이 등록할 수 있습니다.

이미, 웹 어플리케이션 프로젝트를 생성하시면,

public static void RegisterGlobalFilters(GlobalFilterCollection filters){}

이 메쏘드가 생성되어있습니다.

filters.Add(new MyActionFilter()); 로 추가하시면 되겠네요^^


- JsonValueProviderFactory 클래스 :

모델을 바로 JSON 데이터로 바인드해준다네요. 액션 메쏘드에서 파라미터로 JSON 데이터를 주고 받을 수 있는 거죠^^;
자세한 것은 Sending JSON to an ASP.NET MVC Action Method Argument 을 참고해서 보시면 됩니다.


- .NET Framework 4의 메타데이터 속성 지원 :

.NET 4의 DisplayAttribute와 같은 속성들을 지원해준다네요.

등등등 이 있지만, 해봐야 알겠죠^^

- 서비스 로케이션과 DI(Dependency Injection) 지원
- .NET Framework 4 유효성검사 속성과 IValidatableObject를 지원
- New IClientValidatable Interface
- 새로운 액션 타입이 추가되었죠 : HttpNotFoundResult(404 에러), HttpStatusCodeResult


계속 살펴보겠습니다.^^ 관련 글 팍팍 진행하도록 하겠습니다. 물론 mvc 2 얘기가 끝난 것이 아니라 병행하면서요 ㅡ,.ㅡ;;
 
참고자료 : http://www.hanselman.com/blog/ASPNETMVC3Preview1ReleasedChannel9VideoAndHanselminutesPodcast224OhMy.aspx
http://haacked.com/archive/2010/07/27/aspnetmvc3-preview1-released.aspx

Asynchronous Agents Library
– message block 3. ( overwrite_buffer & single_assignment )

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

 

시작하는 글

 지난 글 unbounded_buffer 에 이어 또 다른 message block 인 overwrite_buffersingle_assignment 에 대해서 알아보도록 하겠습니다.

 Message block 들은 특징들이 모두 다르기 때문에 지난 unbounded_buffer 을 생각하시면 이해가 어려울 수 있습니다. message block 하나 하나의 쓰임새가 다르므로 새로운 것을 알아본다고 생각하시는 것이 좋을 것 같습니다.

 

overwrite_buffer< _Type >

 지금부터 설명드릴 overwrite_bufferunbounded_buffer 와는 달리 하나의 변수라고 생각하시면 이해하기 쉬울 것입니다.

 Concurrency runtime 을 사용하지 않고, 스레드 간의 상태나 정보를 공유하려면 전역 변수나 힙( heap ) 에 할당된 변수에 락( lock ) 을 걸어 사용해야 합니다.

 overwrite_buffer 는 방금 언급한 번거로운 작업들을 알아서 해줍니다. 내부에서 힙에 메모리를 할당하고, 접근 시 락을 겁니다. 하지만 사용하는 우리는 그런 것들을 신경 쓰지 않고 마치 지역 변수처럼 사용할 수 있습니다.

 unbounded_buffer 는 외부에서 message 를 받아가면 내부에서 해당 message 가 제거되는 반면에, overwrite_buffer 는 제거되지 않습니다. 또한 하나의 변수와도 같기 때문에 외부에서 message 를 보내면 이 전의 message 를 덮어쓰고 새 message 가 저장됩니다.

 결국 overwrite_buffer 는 단 하나의 message 만을 갖게 됩니다.

 그럼 overwrite_buffer 의 멤버 함수에 대해서 알아보도록 하겠습니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수들입니다.

 

bool has_value() const

 현재 message 를 가지고 있는지 반환합니다.

 어떠한 message 도 갖지 않을 경우에 false 를 반환합니다. 만약, 한번이라도 overwrite_buffer 에 message 가 전달된다면 그 후부터는 true 를 반환합니다. overwrite_buffer 는 외부에서 message 를 받아가도 내부의 message 가 제거되지 않기 때문입니다.

 Message 를 갖고 있지 않을 때, 외부에서 동기 함수인 receive() 를 사용해 message 를 얻기를 원한다면 overwrite_buffer 에 message 가 들어올 때까지 기다립니다. message 가 제거되지 않기 때문에 한번이라도 overwrite_buffer 가 message 를 받으면 receive() 가 기다리는 일은 없을 것입니다.

 

_Type value();

 현재 가지고 있는지 message 를 반환합니다.

 내부적으로 동기 전달 함수인 receive() 를 사용하므로 message 를 가지고 있지 않다면 message 를 갖게 될 때까지 기다립니다. 만약 이 때, has_value() 를 호출했다면 false 를 반환할 것입니다.

 Message 가 제거되지 않기 때문에 전달 함수를 이용해 message 를 받아갈 경우, 복사본이 전달됩니다.

 

예제

 overwrite_buffer 의 간단한 예제를 구현해보도록 하겠습니다.

 

시나리오

 네트워크 지연 시간을 갱신하고, 출력하는 프로그램을 작성할 것입니다.

 네트워크 지연 시간을 갱신하는 역할을 하는 agent 와 갱신된 정보를 출력하는 agent 가 하나의 overwrite_buffer 를 공유하여 사용하는 예제입니다.

 

코드

#include <iostream>
#include <array>
#include <agents.h>

using namespace std;
using namespace Concurrency;

// 지연 시간을 얻어오는 agent.
class PingUpdater
	: public agent
{
public:
	PingUpdater( const array< unsigned int, 5 >& delayTimeSource, ITarget< unsigned int >& targetBlock )
		: delayTimeSource( delayTimeSource )
		, targetBlock( targetBlock ) { }

protected:
	// 2초마다 지연 시간을 얻어 옴.
	void run()
	{
		while( true )
		{
			asend( this->targetBlock, this->GetDelayTime() );

			Concurrency::wait( 2000 );
		}

		this->done();
	}

	// 지연 시간을 시뮬레이션하는 함수.
	unsigned int GetDelayTime()
	{
		static unsigned int index = 0;

		unsigned int delayTime = this->delayTimeSource[ index ];

		if( index + 1 < this->delayTimeSource.size() )
			++index;		
		else
			index = 0;

		return delayTime;
	}

private:
	const array< unsigned int, 5 >&	delayTimeSource;
	ITarget< unsigned int >&		targetBlock;
};

// 지연 시간을 출력하는 agent.
class PingDisplayer
	: public agent
{
public:
	PingDisplayer( ISource< unsigned int >& sourceBlock )
		: sourceBlock( sourceBlock ) { }
protected:
	// 1초마다 지연 시간을 출력한다.
	void run()
	{
		while( true )
		{
			this->Display( receive( this->sourceBlock ) );

			Concurrency::wait( 1000 );
		}

		this->done();
	}

	// 지연 시간을 출력하는 함수.
	void Display( unsigned int delayTime )
	{
		wcout << L"current delay time: " << delayTime << endl;
	}

private:
	ISource< unsigned int >&	sourceBlock;
};

int main()
{
	// 네트워크 지연 시간의 시뮬레이션 정보.
	array< unsigned int, 5 > delayTimeSource = { 210, 211, 261, 246, 223 };

	// 공유 버퍼
	overwrite_buffer< unsigned int > delayTimeBuffer;

	// 네트워크 지연 시간을 갱신하는 agent 와 출력하는 agent.
	PingUpdater updater( delayTimeSource, delayTimeBuffer );
	PingDisplayer displayer( delayTimeBuffer );

	// agent 시작.
	updater.start();
	displayer.start();

	// agent 의 작업이 모두 끝날 때까지 대기.
	agent* waitingAgents[2] = { &updater, &displayer };
	agent::wait_for_all( 2, waitingAgents );
}

[ 코드1. overwrite_buffer 를 이용한 네트워크 지연 시간 갱신 및 출력 예제 ]

 PingUpdater 클래스는 agent 클래스로 네트워크 지연 시간을 갱신하는 역할을 합니다. 2초에 한 번씩 시뮬레이션을 위해 준비된 정보를 순회하며 얻어와서 overwrite_buffer 에 전달합니다.

 PingDisplayer 클래스도 agent 클래스로 갱신된 네트워크 지연 시간을 화면에 출력하는 역할을 합니다. 1초에 한번씩 overwrite_buffer 로부터 갱신된 정보를 가져와서 화면에 출력합니다.

 예제에서 사용된 Concurrency::wait() 는 Win32 API 의 Sleep() 과 같은 역할을 합니다. agent::wait() 과 혼동하지 않길 바랍니다.

 위 코드를 보시면 굉장히 직관적이고, 간단하게 멀티 스레드 프로그래밍을 할 수 있다는 것을 알 수 있을 것입니다.

 

[ 그림1. overwrite_buffer 를 이용한 네트워크 지연 시간 갱신 및 출력 예제 ]

[ 그림1. overwrite_buffer 를 이용한 네트워크 지연 시간 갱신 및 출력 예제 ]

 

single_assignment< _Type >

 single_assignment 는 위에서 설명한 overwrite_buffer 와 거의 흡사합니다.

 단지 다른 점이 있다면 message 를 한번만 받을 수 있다는 것입니다. 만약 두 번 이상 보낸 다면 두 번째부터는 무시됩니다.

 멤버 함수 또한 거의 같지만, 다른 점을 알아보겠습니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 함수들입니다.

 

bool has_value() const

 위에서 설명한 overwrite_bufferhas_value() 와 같습니다.

 Message 를 단 한번도 받지 않았다면 false 를 반환하고, 받았다면 true 를 반환합니다.

 

_Type const & value()

 overwrite_buffervalue() 와 같은 기능을 합니다.

 하지만 값을 반환하지 않고 const 참조를 반환한다는 것이 다릅니다.

 overwrite_buffervalue() 와 마찬가지로 message 를 갖고 있지 않다면 message 를 갖게 될 때까지 기다립니다.

 

마치는 글

 이번 글에서는 overwrite_buffersingle_assignment 에 대해서 알아보았습니다.

  single_assignment 는 overwrite_buffer 와 거의 흡사하기 때문에 예제는 생략하였습니다.

 지난 글에서 본 unbounded_buffer 와는 분명히 쓰임새가 다르므로 특징을 잘 파악해두시면 좋을 것입니다.

 다음 글에서 또 다른 message block 을 소개해드릴 것입니다. 그 message block 또한 쓰임새가 분명히 다르므로 Asynchronous Agents Library 의 활용도가 굉장히 넓다는 것을 아시게 될 것입니다.

[MFC/윈도우 7 멀티터치] #6 : 예제 코드 올립니다

MFC 2010. 7. 26. 09:00 Posted by 알 수 없는 사용자

안녕하세요 ~ MFC 카테고리의 꽃집총각 입니다.

이번에는 그 동안 함께 멀티터치를 공부하면서 다뤄보았던 예제 프로그램들의 소스를 정리해 보았습니다.

아래 코드를 다운 받으세요 :)


Gesture : WM_GESTURE 메세지를 이용해 화면상의 사각형을 이동, 회전, 확대/축소 하는 예제.

Touch : WM_TOUCH 메세지를 이용해 클라이언트 영역에 터치 입력 궤적을 따라 선을 그려주는 예제.

Manipulation : IManipulationProcessor 인터페이스를 활용해 Microsoft Surface에서와 같은 Manipulation 움직임을 제어하는 예제.

Manipulation Inertia : IManipulationProcessor, IInertiaProcessor 인터페이스를 모두 활용, 조작(Manipulation) 및 관성(Inertia) 모두를 적용한 예제.

조작(Manipulation)과 관성(Inertia) 처리 방법은 아직 포스팅으로 설명 드리지 못한 부분입니다만 샘플 코드 정리하면서 함께 정리해 공개합니다.

모든 코드는 기본적으로 MS에서 제공하는 예제 코드를 기반으로 하고, 제가 포스팅 하면서 추가로 변경한 부분들의 코드도 일부 포함되어 있습니다. 참고 부탁 드립니다. 

Asynchronous Agents Library
– message block 2. ( unbounded_buffer )

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

 

시작하는 글

 이전 글에서 message block 의 인터페이스인 ISourceITarget 인터페이스에 대해서 알아보았습니다. 이번 글부터 그 인터페이스들을 상속받아 구현한 message block 에 대해 알아보겠습니다.

 Message block 은 버퍼( buffer ) 를 가질 수도 있고, 상태만 가질 수도 있고, 기능만 가질 수도 있습니다. 그러므로 각 message block 들의 특징을 잘 파악하고, 언제 필요한지 알아야 합니다.

 이번 글에서는 가장 범용적인 유용한 unbounded_buffer 에 대해서 알아보도록 하겠습니다.

 

unbounded_buffer< _Type >

 unbounded_buffer 는 message block 중 가장 많이 사용될 것입니다. unbounded_buffer 는 내부적으로 큐( queue )를 구현하고 있어 message 저장소 역할을 합니다. 누군가 unbounded_buffer 에 message 를 보내면 unbounded_buffer 에 순서대로 차곡차곡 쌓이고, 쌓인 순서대로 꺼내서 쓸 수 있습니다. 꺼낸 message 는 unbounded_buffer 에서 제거됩니다. 그렇기 때문에 여러 곳에서 같은 message 를 꺼내 받을 수 없습니다.

 이런 작업들은 비 동기 agent 들과 사용할 때, 빛을 발합니다.

 unbounded_buffer 는 스레드에 안전하므로 직접 lock 을 하지 않아도 됩니다.

 

생성자

 

unbounded_buffer() – 기본 생성자

 빈 unbounded_buffer 를 생성합니다.

 

unbounded_buffer( filter_method const& _Filter )

 빈 unbounded_buffer 를 생성합니다. 하지만 필터 함수를 지정하여 받을 수 있는 메시지를 거를 수 있습니다.

 이 필터 함수는 bool (_Type const &) 형의 시그니처( signature )를 갖습니다.

 

멤버 함수

 

bool enqueue( _Type const& _Item )

 하나의 message 를 unbounded_buffer 에 보냅니다.

 message 전송이 성공이면 true, 아니면 false 를 반환합니다.

 내부적으로 이 함수는 message 전달 함수인 send() 를 사용합니다. 그리고 send() 의 결과를 반환합니다.

 

_Type dequeue()

 unbounded_buffer 에서 하나의 message 를 꺼냅니다. 꺼낸 message 는 큐에서 제거됩니다.

 꺼내진 message 를 반환합니다. 그리고 꺼내진 message 는 unbounded_buffer 내부에서 제거됩니다.

 enqueue() 와 마찬가지로 내부적으로 message 전달 함수인 receive() 를 사용합니다. receive() 가 반환한 값을 반환합니다.

 

예제

 unbounded_buffer 를 사용하여 작은 시나리오를 구현해보도록 하겠습니다.

시나리오

 윈도우즈 OS 는 사용자의 이벤트들을 메시지 큐에 담고, 큐에 들어온 메시지들을 순차적으로 꺼내서 처리하는 메커니즘을 사용합니다. 이 시나리오를 agent 와 unbounded_buffer 를 이용하여 간단하게 구현해보겠습니다.

코드

#include <iostream>
#include <string>
#include <agents.h>

using namespace std;
using namespace Concurrency;

// 메시지 객체
class Message
{
	wstring		message;

public:
	Message( const wstring& message )
		: message( message ) { }

	const wstring& GetMessage() const
	{
		return this->message;
	}
};

// 메시지를 발생하는 사용자 agent
class User
	: public agent
{
	ITarget< Message >&	messageQueue;

public:
	User( ITarget< Message >& target )
		: messageQueue( target ) { }

	void ClickMouseLButton()
	{
		send( this->messageQueue, Message( L"WM_LBUTTONDOWN" ) );
		send( this->messageQueue, Message( L"WM_LBUTTONUP" ) );		
	}

	void DragMouseLButton()
	{
		send( this->messageQueue, Message( L"WM_LBUTTONDOWN" ) );
		send( this->messageQueue, Message( L"WM_MOUSEMOVE" ) );
		send( this->messageQueue, Message( L"WM_LBUTTONUP" ) );
	}

	virtual void run()
	{
		this->ClickMouseLButton();
		
		Concurrency::wait( 1000 );

		this->DragMouseLButton();

		this->done();
	}
};

// 발생한 메시지들을 처리하는 메시지 펌프 agent
class MessagePump
	: public agent
{
	ISource< Message >&	messageQueue;

public:
	MessagePump( ISource< Message >& source )
		: messageQueue( source ) { }

	void ProcessMessage( const Message& message )
	{
		wcout << message.GetMessage() << endl;
	}

	virtual void run()
	{
		while( true )
		{
			Message message = receive( this->messageQueue );

			this->ProcessMessage( message );		
		}

		this->done();
	}
};

int main()
{
	// 메시지 큐
	unbounded_buffer< Message >	messageQueue;

	// 메시지를 발생하는 사용자와 메시지 펌프
	User user( messageQueue );
	MessagePump messagePump( messageQueue );

	// agent 시작.
	user.start();
	messagePump.start();	

	// agent 의 작업이 모두 끝날 때까지 대기
	agent* agents[] = { &user, &messagePump };
	agent::wait_for_all( 2, agents );
}

[ 코드1. agent 와 unbounded_buffer 를 이용한 메시지 펌프 간략 구현 ]

 Message 클래스는 단순히 문자열을 래핑( wrapping ) 하는 클래스로 큐에 저장되는 메시지를 나타냅니다.

 agent 로 2개를 정의하였는데 하나는 사용자가 이벤트 메시지를 발생하는 것을 흉내 낸 User 클래스이고, 다른 하나는 메시지 펌프를 간략화한 MessagePump 클래스입니다.

 사용자 agent( User 객체 )는 약간의 시간차를 두고 이벤트 메시지를 발생합니다. 발생된 메시지는 메시지 큐에 저장됩니다.

 메시지 펌프 agent 는 메시지가 저장될 때까지 대기하다가 메시지가 저장되면 그 메시지를 받아서 처리합니다. 처리된 메시지는 메시지 큐에서 제거됩니다.

 agent 들의 start() 를 사용하여 작업을 시작하고, 모든 agent 의 작업이 끝날 때까지 기다립니다.

 실제로는 하나의 agent 가 무한 루프를 수행하므로 프로그램이 종료되지 않습니다.

 위의 예제처럼 데이터를 보내고, 순차적으로 받아서 처리하고 싶을 때, 내부적으로 큐가 필요할 때 유용한 message block 이 바로 unbounded_ buffer 입니다.

 멀티 스레드 프로그래밍 시 자료 구조 중 큐가 많이 사용되기 때문에 unbounded_buffer 도 많이 사용하게 될 것입니다.

 

[ 그림1. agent 와 unbounded_buffer 를 이용한 메시지 펌프 간략 구현 결과 ]

[ 그림1. agent 와 unbounded_buffer 를 이용한 메시지 펌프 간략 구현 결과 ]

 

마치는 글

 이번 글에서는 message block 중 가장 사용도가 높은 unbounded_buffer 에 대해서 알아보았습니다. unbounded_buffer 이외에도 다양한 message block 이 있습니다.

 다음 글에서는 overwrite_buffer 라는 message block 에 대해서 알아보도록 하겠습니다.

안녕하세요. 지난 시간에는 jqGrid를 이용해서 리스트를 구현해봤습니다. 정말 맛보기였죠? :)
이번 시간은 실제 데이터베이스에서 데이터 조회, 페이징과 정렬부분을 다루도록 하겠습니다.

먼저 데이터베이스 생성

테이블 구조는 다음과 같습니다.

 컬럼명  데이터 타입
 dirId  int
 name  nvarchar(50)
 phone  nvarchar(50)
 email  nvarchar(50)
 speedDial  decimal(2,0)

그냥 기본세팅이죠^^;

엔터티 모델 클래스를 생성할 건데요, 자세히(?)를 원하신다면 이전 포스팅을 참고해주세요^^;
완료가 되면,


여기까지 잘 오셨죠? 저는 Entity Set Name을 TelDir에서 TelDirSet으로 변경하였습니다. 헷갈려서요^^;;

자. 이제는 본격적(?)으로 살펴볼까요? (어째.. 오늘도 맛보기일것 같은 분위기가 물~씬 풍기시죠? ㅡ,.ㅡ;)

페이징 기능을 달자

지난 뷰페이지에 pager란 id로 div 태그를 추가하겠습니다.

<div id="pager" class="scroll" style="text-align:center;"></div>

테이블 뒤에 추가하시면 됩니다.
그리고, 스크립트 부분도 수정해야겠죠?

    <script src="/Scripts/grid.locale-en.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $("#list").jqGrid({
                url: '<%= Url.Action("EntityGridData", "Home") %>',
                datatype: 'json',
                mtype: 'POST',
                colNames: ['No', '이름', '전화번호', '이메일', '단축다이얼'],
                colModel: [
                  { name: 'dirId', index: 'dirId', width: 40, align: 'center' },
                  { name: 'name', index: 'name', width: 100, align: 'left' },
                  { name: 'phone', index: 'phone', width: 150, align: 'left' },
                  { name: 'email', index: 'email', width: 250, align: 'left' },
                  { name: 'speedDial', index: 'speedDial', width: 100, align: 'center'}],
                    pager: $('#pager'),
                emptyrecords: "Nothing to display",            
                rowNum: 3,
                rowList: [3, 10, 20, 50],
                sortname: 'dirId',
                sortorder: "desc",
                viewrecords: true,

                caption: '전화번호부'
            });
        });
    </script>

추가된 부분은 굵은글씨로 표시하였습니다. 일단, grid.locale-en.js를 추가해야되더라고요^^; 디폴트로 그냥 jqGrid 스크립트를 넣을때 추가하라고 하였는데, 제가 지난 포스팅때는 빠뜨렸죠.
이런 언어 스크립트 파일에는 페이징 관련한 디폴트 값들이 들어가 있습니다.

defaults:{
   recordtext:"View {0} - {1} of {2}",
   emptyrecords:"No records to view",
   loadtext:"Loading...",
   pgtext:"Page {0} of {1}"
  }


나머지 프로퍼티에 대한 설명을 드리자면,
pager는 위 이미지 보이시죠? ^^; 저렇게 레코드들을 이동할수 있게 해주는 페이징 바를 정의합니다.
저같은 경우는 $('#pager')로 jQuery 표현을 썼는데요, jqGrid의 wiki를 보니 '#pager', 'pager', jQuery('#pager') 세가지 경우가 모두 가능한데요. 앞에 두가지 방법을 추천한다네요. 흠. jQuery 변수가 내보내기, 가져오기 모듈을 이용할때 문제를 발생시킬수 있다고 합니다. 이 부분은 차츰(?) 찾아보도록 하죠;;

The definition of the pager in the grid can be done this way:pager : '#gridpager', pager : 'gridpager' or pager : jQuery('#gridpager'). All the three methods are valid, but I recommend to use the first or second one, since the jQuery variant causes problems when we try to use Exporting and Importing modules.

emptyrecords는 말 그대로 데이터가 없을 때 표현할 문구를 나타내고요,
rowNum은 페이지에서 보여줄 레코드 갯수,
rowList는 페이지 갯수를 선택할 수 있도록 하는 셀렉트박스의 옵션들,
sortname, sortorder는 각각 정렬할 컬럼과 정렬방식(오름차순, 내림차순),
viewrecords는 토탈 레코드의 수(위 이미지에서 View 1 -3 of 5)를 표현하는 것을 허용할 것인지 여부를 나타냅니다.

이제 뷰페이지는 완성이 되었고요, 컨트롤러 손봐야겠죠?
EntityGridData() 라는 이름의 액션메쏘드를 추가하겠습니다.

[HttpPost]
        public ActionResult EntityGridData(string sidx, string sord, int page, int rows)
        {
            // 데이터베이스 연결
            MvcDbEntities _db = new MvcDbEntities();

            // 페이징 변수 세팅
            int pageIndex = Convert.ToInt32(page) - 1;
            int pageSize = rows;    // 3
            int totalRecords = _db.TelDirSet.Count();
            int totalPages = (int)Math.Ceiling((float)totalRecords / (float)pageSize);

            // 데이터 조회(페이징&정렬)
            // sidx : dirId
            // sord : desc
            var dirs = _db.TelDirSet
                .OrderBy("it." + sidx + " " + sord)
                .Skip(pageIndex * pageSize)
                .Take(pageSize)
                .ToList();

            var jsonData = new
            {
                total = totalPages,
                page = page,
                records = totalRecords,
                rows = (
                  from dir in dirs
                  select new
                  {
                      i = dir.dirId,
                      cell = new string[] {
                          dir.dirId.ToString(), dir.name.ToString(), dir.phone.ToString(), dir.email.ToString(), dir.speedDial.ToString()                         
                      }
                  }).ToArray()
            };
            return Json(jsonData);
        }

궁금해 보이는 것이 없죠? ㅎㅎ
jqGrid가 EntityGridData를 호출할때 파라미터(sidx : dirId, sord : desc, page : 1, rows : 3)를 날립니다~~~
실행을 해보면,


너무 간단하게 페이징 기능이 완성되었습니다^^
네이게이션 기능 되고요~ 셀렉트박스로 로우 갯수 선택 기능 되고요~ No탭 클릭하시면 정렬 기능 됩니다요~

마무리요

실행화면 출력하고 보니 아직도 맛!보!기! 인것을 보면 아직 한참 멀은 듯 합니다.
더 알찬 정보로 준비하도록 하겠습니다^^
감사합니다. 


참고자료 :
http://haacked.com/archive/2009/04/14/using-jquery-grid-with-asp.net-mvc.aspx
http://www.trirand.com/jqgridwiki/doku.php?id=wiki:pager&s[]=paging&s[]=properties

M, V 그리고 C의 각방생활(9) - jqGrid 사용해보자

ASP.NET MVC 2010. 7. 14. 09:00 Posted by 네버덜레스
이번 시간은 jQuery 플러그인인 jqGrid를 잠깐(?) 사용해보는 시간을 갖도록 하겠습니다. 

jqGrid 플러그인 다운

먼저, jqGrid 사이트에서 jqGrid 플러그인을 다운받습니다.
다운받은 압축파일을 푸신 후, ASP.NET MVC 프로젝트에 3개의 파일을 추가하겠습니다. jquery.jqGrid.min.js 파일과 jquery-ui-1.7.1.custom.css, ui.jqgrid.css 파일입니다.


자, 이제 시작해볼까요?

jqGrid 맛보기

한꺼번에 다 보여드리기 보다는 조금조금씩~ 맛을 보여드리도록 하겠습니다^^

좀전에 프로젝트에 추가한 파일을 뷰페이지에 쭈~욱 끌어다 놓습니다.

<link href="/Content/jqGrid/jquery-ui-1.7.1.custom.css" rel="stylesheet" type="text/css" />
<link href="/Content/jqGrid/ui.jqgrid.css" rel="stylesheet" type="text/css" />
<script src="/Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script src="/Scripts/jqGrid/jquery.jqGrid.min.js" type="text/javascript"></script>

그 다음으로, 이 jqGrid 관련 자바스크립트 소스를 추가하겠습니다. 한눈에 봐도 너무 간단한 스크립트 부분이라 jQuery를 모르셔도 딱!! 파악하실수 있을 겁니다.

<script type="text/javascript">
        $(function () {
            $("#list").jqGrid({
                url: '<%= Url.Action("GridData", "Home") %>',
                datatype: 'json',
                mtype: 'get',
                colNames: ['No', '이름', '전화번호', '이메일', '단축다이얼'],
                colModel: [
                  { name: 'DirId', index: 'DirId', width: 40, align: 'center' },
                  { name: 'Name', index: 'Name', width: 100, align: 'left' },
                  { name: 'Phone', index: 'Phone', width: 100, align: 'left' },
                  { name: 'Email', index: 'Email', width: 200, align: 'left' },
                  { name: 'SpeedDial', index: 'SpeedDial', width: 100, align: 'center'}],
                caption: '전화번호부'
            });
        });
    </script> 

위 소스를 잠깐 살펴보면, url은 Home 컨트롤러에서 GridData라는 액션메쏘드를 호출하고 있습니다. 잠시 후에 이를 구현해야겠죠?^^; datatype은 json이네요. 음.. GridData라는 놈이 json객체를 넘겨주겠군?! 하고 생각하시면 되죠. colNames는 리스트를 보여줄때 각각의 컬럼을 구분짓는 이름입니다. colModel을 통해 grid에서 받을 리스트에 width라던지 align을 주고 있는 것을 보실수 있습니다.

그래서 완성된 Index.aspx 뷰페이지를 보게되면,

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    홈 페이지
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <link href="/Content/jqGrid/jquery-ui-1.7.1.custom.css" rel="stylesheet" type="text/css" />
    <link href="/Content/jqGrid/ui.jqgrid.css" rel="stylesheet" type="text/css" />
    <script src="/Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
    <script src="/Scripts/jqGrid/jquery.jqGrid.min.js" type="text/javascript"></script>
 
    <script type="text/javascript">
        $(function () {
            $("#list").jqGrid({
                url: '<%= Url.Action("GridData", "Home") %>',
                datatype: 'json',
                mtype: 'GET',
                colNames: ['No', '이름', '전화번호', '이메일', '단축다이얼'],
                colModel: [
                  { name: 'DirId', index: 'DirId', width: 40, align: 'center' },
                  { name: 'Name', index: 'Name', width: 100, align: 'left' },
                  { name: 'Phone', index: 'Phone', width: 100, align: 'left' },
                  { name: 'Email', index: 'Email', width: 200, align: 'left' },
                  { name: 'SpeedDial', index: 'SpeedDial', width: 100, align: 'center'}],
                caption: '전화번호부'
            });
        });
    </script> 
<h2><%: ViewData["Message"] %></h2>
<table id="list" class="scroll" cellpadding="0" cellspacing="0"></table>
</asp:Content>

table에 리스트를 쫙~ 뿌려주도록 하겠습니다.

이제, Home 컨트롤러를 잠깐 손보도록 하겠습니다. jqGrid에서 받을 json객체를 리턴하는 GridData라는 액션메쏘드를 만들도록 하겠습니다. 이번 포스팅은 정말 맛보기이기 때문에 간단하게 바로 객체를 만들어서 리턴하겠습니다.


와우~ 정말 간단하게 모든 구현이 완료되었습니다! 이제 실행을 해볼까요?


엥? 이건 또 뭔가요? 역시 한번에 되는 것은 없나봐요;;
내용을 보니 GET 요청이 차단되었고, JsonRequestBehavior를 AllowGet으로 설정하라고?! 호출 스택을 보니 JsonResult를 실행하다가 에러가 발생하였네요. 음.. JsonResult 부분이 잘못되었군. 한번 수정해보죠^^;

return Json(dirs, JsonRequestBehavior.AllowGet);

수정후 실행해보면~


네. 멋지게 성공하였습니다.

ASP.NET MVC 2 에서는 기본적으로 이러한 GET방식의 호출을 보안상의 문제로 막아놨습니다. 그래서 JSON 객체를 리턴할때는 JsonRequestBehavior.AllowGet을 추가하여 클라이언트의 GET요청을 허용하도록 한 것이죠.

하지만, 막아놓은 것을 굳이 풀 필요는 없겠죠?^^; POST 방식으로 호출하는 것이 좀더 좋을 듯 합니다.
이 부분은 따로 설명드릴 필요없겠죠? 약간(^^;;) 설명드리면~
일단 GridData액션 메쏘드 위에 GET으로 요청한 놈은 접급하지마! 라는 표지판([HttpPost])을 세워두는거죠. 실행시켜보면 역시 에러가 발생할겁니다. 리소스를 찾을수 없다는... 그래서! 뷰페이지 스크립트 부분의 mtype을 POST로 수정하는거죠^^ 실행해보세요~ 잘되시나요?

이거슨 번외요~

웹 개발자를 위한 Web Development Helper 유틸이 있습니다. 익스플로러에 확장할 수 있죠. 저같은 경우는 Fiddler를 많이(?) 사용하는데요. Web Development Helper는 특히 Ajax와 ASP.NET 개발자를 위한 것이라고 하네요. 사이트에서 다운 받고 인스톨하시고, 익스플로러의 도구 메뉴의 탐색창->Web Development Helper를 클릭하시면 됩니다.
이번 jqGrid 맛보기에서의 request&response정보도 확인할 수 있네요.


마무리요

이번시간은 정말 jqGrid 플러그인의 맛보기였고요, 다음 포스팅에서 뵙도록 하겠습니다. (다음 포스팅도 맛보기처럼 보이면 어떡하죠?^^;;)

참고자료 :
http://haacked.com/archive/2009/04/14/using-jquery-grid-with-asp.net-mvc.aspx
http://www.trirand.net/
http://projects.nikhilk.net/WebDevHelper/
http://geekswithblogs.net/michelotti/archive/2008/06/28/mvc-json---jsonresult-and-jquery.aspx