- 주최 : 한국 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 할 예정이다.
- 서비스 로케이션과 DI(Dependency Injection) 지원
- .NET Framework 4 유효성검사 속성과 IValidatableObject를 지원
- New IClientValidatable Interface - 새로운 액션 타입이 추가되었죠: HttpNotFoundResult(404 에러), HttpStatusCodeResult
계속 살펴보겠습니다.^^ 관련 글 팍팍 진행하도록 하겠습니다. 물론 mvc 2 얘기가 끝난 것이 아니라 병행하면서요 ㅡ,.ㅡ;;
지난 글 unbounded_buffer 에 이어 또 다른 message block 인 overwrite_buffer 와 single_assignment 에 대해서 알아보도록 하겠습니다.
Message block 들은 특징들이 모두 다르기 때문에 지난 unbounded_buffer 을 생각하시면 이해가 어려울 수 있습니다. message block 하나 하나의 쓰임새가 다르므로 새로운 것을 알아본다고 생각하시는 것이 좋을 것 같습니다.
overwrite_buffer< _Type >
지금부터 설명드릴 overwrite_buffer 은 unbounded_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
#include
#include
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 를 이용한 네트워크 지연 시간 갱신 및 출력 예제 ]
single_assignment< _Type >
single_assignment 는 위에서 설명한 overwrite_buffer 와 거의 흡사합니다.
단지 다른 점이 있다면 message 를 한번만 받을 수 있다는 것입니다. 만약 두 번 이상 보낸 다면 두 번째부터는 무시됩니다.
멤버 함수 또한 거의 같지만, 다른 점을 알아보겠습니다.
멤버 함수
생성자와 소멸자를 제외한 public 인 함수들입니다.
bool has_value() const
위에서 설명한 overwrite_buffer 의 has_value() 와 같습니다.
Message 를 단 한번도 받지 않았다면 false 를 반환하고, 받았다면 true 를 반환합니다.
_Type const & value()
overwrite_buffer 의 value() 와 같은 기능을 합니다.
하지만 값을 반환하지 않고 const 참조를 반환한다는 것이 다릅니다.
overwrite_buffer 의 value() 와 마찬가지로 message 를 갖고 있지 않다면 message 를 갖게 될 때까지 기다립니다.
마치는 글
이번 글에서는 overwrite_buffer 와 single_assignment 에 대해서 알아보았습니다.
single_assignment 는 overwrite_buffer 와 거의 흡사하기 때문에 예제는 생략하였습니다.
지난 글에서 본 unbounded_buffer 와는 분명히 쓰임새가 다르므로 특징을 잘 파악해두시면 좋을 것입니다.
다음 글에서 또 다른 message block 을 소개해드릴 것입니다. 그 message block 또한 쓰임새가 분명히 다르므로 Asynchronous Agents Library 의 활용도가 굉장히 넓다는 것을 아시게 될 것입니다.
이전 글에서 message block 의 인터페이스인 ISource 와 ITarget 인터페이스에 대해서 알아보았습니다. 이번 글부터 그 인터페이스들을 상속받아 구현한 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
#include
#include
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 를 이용한 메시지 펌프 간략 구현 결과 ]
마치는 글
이번 글에서는 message block 중 가장 사용도가 높은 unbounded_buffer 에 대해서 알아보았습니다. unbounded_buffer 이외에도 다양한 message block 이 있습니다.
다음 글에서는 overwrite_buffer 라는 message block 에 대해서 알아보도록 하겠습니다.
추가된 부분은 굵은글씨로 표시하였습니다. 일단, 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);
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);
}
이번 시간은 jQuery 플러그인인 jqGrid를 잠깐(?) 사용해보는 시간을 갖도록 하겠습니다.
jqGrid 플러그인 다운
먼저, jqGrid 사이트에서 jqGrid 플러그인을 다운받습니다.
다운받은 압축파일을 푸신 후, ASP.NET MVC 프로젝트에 3개의 파일을 추가하겠습니다. jquery.jqGrid.min.js 파일과 jquery-ui-1.7.1.custom.css, ui.jqgrid.css 파일입니다.
위 소스를 잠깐 살펴보면, 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>
이제, 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 플러그인의 맛보기였고요, 다음 포스팅에서 뵙도록 하겠습니다. (다음 포스팅도 맛보기처럼 보이면 어떡하죠?^^;;)
이전 글까지 Asynchronous Agents Library( 이하, AAL ) 의 일부인 agent 와 message 전달 함수에 대해 알아보았습니다. agent 만 알아도 어느 정도 비 동기 처리를 쉽게 구현할 수 있습니다.
이번 글에서는 agent 간 소통을 할 수 있는 message block 들에 대해서 알아보겠습니다. message block 을 이용하면 agent 간 데이터 또는 상태 동기화를 할 수 있습니다.
AAL 은 스레드로부터 안전한 방식으로 구현되었고, 추상화되었습니다. 그래서 agent 객체와 message block 을 이용한 동기화 로직이 직관적이고 쉽게 흐름을 파악할 수 있어 데드락( dead-lock ) 을 방지하기 용이합니다.
그럼 지금부터 agent 를 이용한 비 동기 처리에 날개를 달아주는 message block 에 대해 알아보도록 하겠습니다.
Message 객체
예전 글부터 message, message 메커니즘, message 전달 함수, message block 등을 언급하면서 항상 message 란 개념을 사용했습니다.
이 개념은 실제 클래스로 존재합니다. 하지만 단지 message 를 래핑( wrapping ) 할 뿐, 전혀 다른 기능을 가지고 있지 않은 클래스입니다.
한 가지 기능이 있다면 식별자( id )를 갖는다는 것입니다. message 클래스는 Concurrency Runtime 의 _Runtime_object 클래스를 상속 받습니다. 이 클래스는 Runtime 에 의해 생성될 때 자동으로 id 를 갖게 됩니다. 이 id 를 알아보는 함수는 msg_id() 입니다. 이 메서드의 접근자가 public 으로 되어 있어 message 클래스에서도 사용 가능합니다.
이 msg_id() 가 반환한 값은 message block 에서 사용되는 runtime_object_identity 형입니다. 몇몇 message block 메서드의 runtime_object_identity 형의 매개변수에 인자로 사용할 수 있습니다.
사실, 직접 message block 을 구현하지 않는 한, message 클래스는 직접 사용할 경우는 없을 것입니다. 우리는 보내고 받는 데이터를 공급하면 내부적으로 그 데이터를 message 클래스로 래핑하고 message block 내부에서 사용하게 되는 것입니다. 그러므로 크게 신경쓰지 않아도 됩니다.
Source 와 target
Message block 은 크게 두 가지 종류로 나눌 수 있습니다. 하나는 source 이고 다른 하나는 target 입니다.
Message block 에서의 source 는 message 를 보낼 message block 을 일컫습니다. 마찬가지로 target 은 message 를 받을 message block 을 뜻합니다.
ISource 인터페이스
Source 는 AAL 의 하나의 개념이지만, 이것을 인터페이스로 추상화 하였습니다. 이것이 ISource 인터페이스입니다.
그러므로 source 로 쓰일 message block 들은 ISource 인터페이스를 상속하여 구현되었습니다. 만약 직접 source 로 사용될 message block 을 구현하신다면
ISource 인터페이스를 상속해야 합니다.
- ISource 인터페이스의 선언
template< class _Type>class ISource;
[ 코드1. ISource 인터페이스의 선언 ]
템플릿 매개변수인 _Type 은 message 로 쓰일 데이터 형( type )입니다. _Type 은 public typedef 인 source_type 으로 사용할 수 있습니다.
link_target() 은 target 인 message block 과 연결합니다. 여기서 연결의 의미는 자동으로 전달된다는 의미로 생각하시면 되겠습니다.
즉, 이 ISource 를 상속받은 message block 에 link_target() 으로 target message block 을 연결했을 경우, 이 message block 의 message 들은 직접 전달 함수를 사용하지 않아도 자동으로 target message block 으로 전달됩니다.
연결할 target 은 여러 개일 수 있습니다. 그러나 ISource 를 상속한 message block 의 구현에 따라 첫 번째 target 만 동작할 수도 있습니다. 예로 unbounded_buffer 가 있습니다. unbounded_buffer 는 내부적으로 큐를 구현하고 있어 전달 후, message 가 큐에서 제거되므로 두 번째 연결된 target 이 있더라도 message 를 보낼 수 없습니다.
매개변수인 _PTarget 은 연결할 target message block 입니다. _PTarget 의 데이터 형인 ITarget 은 target 을 추상화한 인터페이스입니다. 곧 설명하도록 하겠습니다.
accept() 는 target 에서 호출되지만, source 가 제공합니다. source 의 message 를 수락하고, 소유권이 이전됩니다.
매개변수인 runtime_object_identity 는 message 객체의 msg_id() 로 얻을 수 있습니다. 실제로 runtime_object_identity 는 __int32 를 typedef 한 것이고, Concurrency Runtime 에서 객체를 생성할 때 지정되는 고유의 번호입니다.
release_ref() 는 참조 개수를 감소시킵니다. 현재 link_target() 으로 연결된 target 에서 호출됩니다.
매개변수인 _PTarget 은 link_target() 으로 연결된 target 입니다.
ITarget 인터페이스
Target 또한 source 와 마찬가지로 AAL 의 하나의 개념이지만, 이것을 추상화 하였습니다. 이것이 ITarget 인터페이스 입니다.
Target 으로 사용할 message block 을 구현하신다면 ITarget 인터페이스를 상속해야 합니다.
- ITarget 인터페이스의 선언
template< class _Type>class ITarget;
[ 코드11. ITarget 인터페이스의 선언 ]
템플릿 매개변수인 _Type 은 message 로 사용될 데이터 형입니다. _Type 은 public typedef 인 type 으로 사용할 수 있습니다.
Target 은 필터를 지정할 수 있습니다. 그래서 필터 함수의 시그니처( signature )인 bool ( _Type const & ) 를 typedef std::tr1::function<bool(_Type const&)> filter_method 로 정의되어 있습니다.
Asynchronous Agents Library
– message 전달 함수. 2 ( 수신 )
작성자: 임준환( mumbi at daum dot net )
Message 수신
Message 를 message block 에 전송할 수 있듯이, message block 으로부터 수신할 수도 있습니다. message 수신 함수에도 전송 함수와 마찬가지로 동기 함수인 receive() 와 비 동기 함수인 try_receive() 가 있습니다.
동기 함수 receive()
동기 함수인 receive() 는 message block 으로부터 수신이 완료될 때 수신된 message 를 반환합니다. 만약 message block 에 어떠한 message 도 없다면 receive() 는 message block 에 수신할 message 가 있을 때까지 기다립니다.
아래는 receive() 의 선언입니다.
template <
class _Type
>
_Type receive(
ISource<_Type> * _Src,
unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE
);
template <
class _Type
>
_Type receive(
ISource<_Type> * _Src,
filter_method const& _Filter_proc,
unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE
);
template <
class _Type
>
_Type receive(
ISource<_Type> &_Src,
unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE
);
template <
class _Type
>
_Type receive(
ISource<_Type> &_Src,
filter_method const& _Filter_proc,
unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE
);
[ 코드1. receive() 의 선언 ]
템플릿 매개변수인 _Type 은 message 의 자료 형입니다.
함수 매개변수 중 _Src 는 message block 의 인터페이스 중 하나인 ISource 를 상속한 message block 객체이며, 이 객체로부터 message 를 수신합니다.
함수 매개변수 중 _Timeout 은 최대 대기 시간입니다. 이것은 receive() 가 동기 함수이기 때문에 영원히 기다릴 상황을 대비하는 방법입니다. 이 매개변수를 지정했을 때, 최대 대기 시간을 초과하였을 경우, agent::wait()( Asynchronous Agents Library – agent. 2 ( 기능 ) 참고 ) 와 마찬가지로 operation_timed_out 예외를 발생합니다. 그러므로 이 매개변수를 지정 시 반드시 해당 예외를 처리해주어야 합니다. 기본 인자로 COOPERATIVE_TIME_INFINITE 가 지정되어 있으며, 무한히 기다리는 것을 의미합니다.
함수 매개변수 중 _Filter_proc 는 message 를 거부할 수 있는 필터입니다. message block 생성자로 지정할 수 있는 필터와 마찬가지로 std::tr1::function<bool(_Type const&)> 입니다.
템플릿 매개변수인 _Type 은 receive() 와 마찬가지로 message 의 자료 형입니다.
함수 매개변수 중 _Src 도 receive() 와 마찬가지로 message block 의 인터페이스 중 하나인 ISource 를 상속한 message block 객체이며, 이 객체로부터 message 를 수신합니다.
함수 매개변수 중 _value 는 수신한 message 를 저장할 변수의 참조입니다. 수신이 성공하면 message 는 이 참조가 가리키는 변수에 저장됩니다.
함수 매개변수 중 _Filter_proc 는 receive() 와 마찬가지로 message 를 거부할 수 있는 필터입니다.
try_receive() 는 수신의 완료를 기다리지 않기 때문에 수신을 시도했을 때( try_receive() 를 호출했을 때 ) message block 에 어떠한 message 도 없다면 false 를 반환해 알려줍니다. message 가 있다면 true 를 반환합니다.
만약, 수신 시도를 하자마자 시도한 컨텍스트가 계속 진행되기를 원한다면 receive() 에 _Timeout 매개변수에 0 을 지정하기 보다는 try_receive() 를 사용하는게 바람직합니다.
동기 함수인 receive() 와는 달리 비 동기 함수인 try_receive() 는 수신할 message 가 없을 경우, 기다리지 않고 false 를 반환합니다.
수신할 message 가 있든 없든 바로 반환해야 하는 경우라면 receive() 의 매개변수인 최대 대기 시간을 0 으로 지정하는 것보다는 try_receive() 를 권장합니다.
receive() 의 최대 대기 시간은 예외 메커니즘을 사용하므로 try_receive() 에 비해 오버헤드가 있을 수 있습니다.
[ 그림4. try_receive() 의 수신할 message 가 없는 경우 예제 실행 결과 ]
Message 필터
지난 글에서 message block 에 필터를 지정할 수 있다고 언급했습니다. message block 에 지정되는 필터는 message block 에 전송 시에 적용됩니다.
마찬가지로 수신 함수들에도 필터를 지정할 수 있습니다. message block 으로부터 수신 시에 적용됩니다.
동기 함수인 receive() 는 수신할 message 가 필터에 의해 수락될 때까지 대기합니다. 즉, 수신할 message 가 필터에 의해 거부된다면 message block 에 message 가 없을 때와 같습니다.
비 동기 함수인 try_receive() 또한 message block 에 message 가 없을 때와 마찬가지로 false 를 반환합니다.
다시 말해, message block 에 message 가 없는 경우에도 필터에 의해 거부된다는 말과 같습니다.
그럼, 필터를 이용한 수신 함수에 대한 예제를 살펴보겠습니다.
예제
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace std::tr1;
using namespace Concurrency;
class number_collector
: public agent
{
public:
number_collector( ISource< int >& source, vector< int >& result, function< bool ( int ) > filter )
: source( source )
, result( result )
, filter( filter ) { }
protected:
void run()
{
while( true )
{
int number = receive( this->source, this->filter );
if( 0 == number )
break;
this->result.push_back( number );
}
this->done();
}
private:
ISource< int >& source;
vector< int >& result;
function< bool ( int ) > filter;
};
int main()
{
// message block
unbounded_buffer< int > message_block;
// send number 1 ~ 10.
parallel_for( 1, 11, [&]( int number )
{
send( message_block, number );
} );
// send stop signal.
send( message_block, 0 ); // for even.
send( message_block, 0 ); // for odd.
vector< int > even_number_array, odd_number_array;
number_collector even_number_collector( message_block, even_number_array, []( int number ) -> bool
{
return 0 == number % 2;
} );
number_collector odd_number_collector( message_block, odd_number_array, []( int number ) -> bool
{
if( 0 == number )
return true;
return 0 != number % 2;
} );
even_number_collector.start();
odd_number_collector.start();
// wait for all agents.
agent* number_collectors[2] = { &even_number_collector, &odd_number_collector };
agent::wait_for_all( 2, number_collectors );
// print
wcout << L"odd numbers: ";
copy( odd_number_array.begin(), odd_number_array.end(), ostream_iterator< int, wchar_t >( wcout, L" " ) );
wcout << endl << L"even numbers: ";
copy( even_number_array.begin(), even_number_array.end(), ostream_iterator< int, wchar_t >( wcout, L" " ) );
wcout << endl;
}
[ 코드7. 필터를 이용한 숫자 고르기 예제 ]
우선 message block 에 1 ~ 10 의 정수를 전송합니다. parallel_for() 를 사용하였는데 이 함수는 Concurrency Runtime 위에서 AAL 과 작동하는 돌아가는 Parallel Patterns Library( 이하, PPL ) 에서 제공하는 함수입니다. PPL 에 대한 자세항 사항은 visual studio 팀 블로그에서 확인하실 수 있습니다.
parallel_for() 는 반복될 내용을 병렬로 처리하기 때문에 성능에 도움을 줍니다. 그러나 반복되는 순서를 보장하지 않습니다.
그래서 1 ~ 10 의 정수가 전송되는 순서는 알 수 없습니다. 하지만 1 ~ 10 의 정수를 모두 전송한 뒤, 0을 보내서 마지막 message 라는 것을 알려주었습니다. 두 번 보낸 0 중 하나는 짝수를 수신하는 agent 를 위한 것이고, 하나는 홀수를 수신하는 agent 를 위한 것입니다.
사실, 이런 처리 로직을 구성할 때에는 상태 변화 알림에 유용한 다른 message block 을 사용하는 것이 좋지만 아직 message block 에 대해서 설명하지 않았기 때문에 혼란을 줄이기 위해 간단한 unbounded_buffer 하나만으로 처리하였습니다.
위 코드에 정의된 agent 인 number_collector 는 message block 으로부터 필터에 의해 필터링된 message 를 컨테이너에 저장합니다.
동기 함수인 receive() 를 사용했기 때문에 원하는 message 가 올 때까지 기다립니다. 이로 인해 필요한 만큼의 최소의 반복을 하여 오버헤드가 줄어 듭니다.
만약 비 동기 함수인 try_receive() 를 사용했다면 쓸모 없는 반복 오버헤드를 발생시킬 것입니다. 이 예제의 경우에는 동기 함수인 receive() 가 적합합니다.
정의된 agent 를 짝수용과 홀수용을 선언하고 start() 를 사용하여 작업을 시작합니다. 그리고 wait_for_all() 을 사용하여 두 agent 가 모두 끝날 때까지 기다린 후, 모든 작업이 종료되면 화면에 수집한 정수들을 출력합니다.
위 예제 코드는 Visual studio 2008 부터 지원하는 tr1 의 function 과 visual studio 2010 부터 지원하는 C++0x 의 람다를 사용하였습니다. Concurrency Runtime 은 tr1, C++0x 등의 visual studio 2010 의 새로운 feature 들을 사용하여 구현되었기 때문에 이것들에 대해 알아두는 것이 좋습니다.
[ 그림5. 필터를 이용한 숫자 고르기 예제 실행 결과 ]
마치는 글
이 글에서는 message 전달 함수 중 수신 함수인 receive() 와 try_receive() 에 대해서 알아보았습니다.
receive() 와 try_receive() 는 사용해야 할 상황이 분명히 다르니 상황에 따라 사용에 유의해야 합니다.
다음 글에서는 message 가 저장되는 message block 에 대해서 알아보도록 하겠습니다.
안녕하세요. 늦바람이 무섭다고 하는데요. jQuery를 향한 늦바람이 불어주길 바라는 1인입니다. ㅎㅎ
이렇게 간단해도 되는겨?
이번 포스팅을 준비하면서 정말 jQuery의 놀라운 힘에 다시 한번 놀랐습니다. 이렇게 간단히 탭메뉴를 넣는게 가능했던건가요?
준비물 준비
먼저, jQueryUI 사이트에서 jquery-ui-1.8.2.custom.zip 파일을 다운받습니다. 압축을 푸시면 jquery-ui-1.8.2.custom.min.js 와 jquery-ui-1.8.2.custom.css 파일이 있습니다.(각각 js폴더와 css폴더에 있습니다.) 이 두 파일을 프로젝트의 Content와 Scripts 폴더에 추가시킵니다. 이제 준비는 됐고요.
준비끝! 예제로!
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>
$("#tabs").tabs() 이게 바로 그 놀라운 능력을 가진 탭메뉴를 가능케하는 힘입니다. (자세한 것은 다음으로 미루고~ 언제가 될지는 몰라요. 그냥 잘 쓰면 되는거죠^^;;)
아. 추가시켰던 두 파일은 마스터페이지에 끌어다놨습니다.
이제 Html.ActionLink() 에 걸린 액션들만 만들어 주면 됩니다.
요청 컨트롤러입니다. 첫 Index() 액션 메쏘드를 보시면 dynamic 이라는 타입이 보이는데요. 처음에는 Contact()와 마찬가지로 PartialView()만 리턴을 하였는데, 파샬뷰를 생성해 놓고 보니.
저렇게 dynamic 이 눈에 딱 띄는바람에 어쩔수(?) 없이 dynamic데이터를 전달하게 되었습니다. 간단히 설명드리면 dynamic은 대인배의 마음 씀씀이를 갖고 있어서 어떤 타입이던지 모두 수용합니다.(컴파일타임에는 터치를 안합니다. 귀찮아서 런타임한테 넘기는거죠;;) dynamic으로 선언된 변수에 멤버, 메쏘드, string, int 가리지 말고 막 넣어주세요. 다 받아줍니다. 하.. 저도 대인배로 살아가야 할텐데 참.. 아쉽습니다 :)
다음 기회에 dynamic에 대해 좀더 자세히 알아보면 좋겠네요.
다시 본론으로 넘어와서, /Product/Index.ascx 를 보시면
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<h3><%: Model.Message %></h3>
<p>
ASP.NET MVC에 대한 자세한 내용을 보려면 <a href="http://asp.net/mvc" title="ASP.NET MVC 웹 사이트">http://asp.net/mvc</a>를 방문하십시오.
</p>
Model객체의 Message의 접근하면(Model.Message) Index 메쏘드에서 넘겨준 다이나믹한 메시지를 받을 수 있습니다.
너무 간단해서 할말을 잃게 만들죠. foreach문을 통해 루프를 돌면서 데이터를 출력합니다.
나머지 Contact.ascx는 안보셔도 됩니다.^^;
자, 완료가 되었으니 확인을 해보죠.
페이지 로드 없이 깔끔하게 탭기능이 완성되었습니다.
마무리요
일반적인 페이지(aspx)도 탭메뉴로 가능합니다. 파샬뷰를 사용한 것은 한 페이지 전체보다 불필요한 부분을 제거한(head, html, body가 보시다시피 파샬뷰에는 존재하지 않습니다.) 간결함때문이랄까요? ㅎㅎ
다음은 더 재미있는 것으로 찾아뵙겠습니다. (지금은 재밌다는겨? 뭐여? ㅡ.ㅡ 이렇게 생각하시는 분은 제발 없으시길 바래요^^)
jQuery is a new kind of JavaScript Library.
jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.
쭉 보면, jQuery는 자바스크립트 라이브러리의 한 종류입니다. jQuery는 신속한 웹 개발을 위한 HTML 문서 탐색, 이벤트 처리, 애니메이션, Ajax와의 상호작용을 간단하게하는 빠르고 간결한 자바스크립트 라이브러리입니다. jQuery는 자바스크립트 작성 방식의 전환을 위해 설계되었습니다.
다른건 몰라도, 암튼 웹 개발을 빠르게 해준다니까 오케이입니다. 귀찮은 작업도 간결하게 해주는 것 같고요.
Visual Studio 에 jQuery가 탑재되어있는 것도 마이크로소프트가 이를 지원한다는 얘기? 그래서 오케이. 스캇 구쓰리의 블로그를 보시면 계속 jQuery 플러그인 얘기가 올라오고 있는 것으로 봐서 활발하게 개발중인 것 같습니다.
간단 예제
정말 간단한 예제를 한번 살펴보도록 하겠습니다. 실망하시면 안~되요.
지금 하려는 것은 'ASP.NET MVC에 대한 자세한 내용을 보려면...' 이 있는 p 태그의 스타일을 변경해 볼겁니다. 먼저 MVC 프로젝트를 새로 생성하겠습니다. 그 다음, Index.aspx 페이지에 Contents 폴더에 있는 jquery-1.4.1.js 파일을 끌어다 놓습니다. '$(' 입력해보시면 놀랍게도 정말 놀~랍게도 인텔리센스를 지원해주는 것을 확인하실 수 있습니다. 멋지죠? :-)
아. 이거할때가 아닌데 좋아하고 있었네요. 먼저 필요한 스타일을 추가하겠습니다. 간단합니다.
지난 아티클에서 Visual Studio 2008 과 Visual Studio 2010 을 동일한 소스 코드와 프로젝트로 개발하기 위한 환경을 구성하는 방법을 알아보았습니다.
문제 원인
하지만 지난 시간에 언급한 듯이 테스트 프로젝트가 포함된 경우는 VS2008 과 VS2010 을 동시에 사용할 수 없는 문제가 발생합니다. 그 이유는 Microsoft.VisualStudio.Quality 프레임워크가 개선이 되고, Microsoft.VisualStudio.TestTools 프레임워크가 도입되면서 기존의 테스트 프레임워크와 비호환적인 부분이 존재하게 됩니다.
아래와 같이 VS2008 에서 작업한 테스트 프로젝트가 있을 경우,
지난 아티클의 방법으로 프로젝트를 변환하게 되면 아래의 오류가 발생합니다.
기존 프로젝트는 기존의 .NET Framework 버전을 그대로 사용할 수 있지만, 테스트 프로젝트는 반드시 .NET Framework 4.0 으로만 사용할 수 있습니다.
만약 하위 프레임워크 버전인 .NET Framework 3.5 로 변경하고자 할 경우 아래와 같은 오류 메시지가 나타나고, 다시 .NET Framework 4.0 버전으로 변경이 됩니다.
일부 이미 완성된 테스트 프로젝트인 경우 이것보다 더 다양한 오류 메시지를 볼 수 있습니다.^^;
어쨌든, Microsoft.VisualStudio.QualiltyTools 프레임워크가 .NET Framework 4.0 버전으로 고정되어 자칫 테스트 프로젝트가 굉장히 큰 우범을 저지를 수 있는 문제가 될 수 있습니다.
반대로 Visual Studio 2010 에서 만든 테스트 프로젝트는 .NET Framework 4.0 이 기본이고 Microsoft.VisualStudio.QualityTools 프레임워크와 Microsoft.VisualStudio.TestTools 프레임워크가 기본 참조(RTM 에서는 제외 됨)이며, 이 프레임워크의 버전도 4.0.xxxxx 버전이라 하위 버전과 호환되지 않습니다.
VS2010 이든 VS2008 이든 테스트 프로젝트가 한번 VS2010 으로 업그레이드 되었다면, VS2008 에서 테스트 프로젝트를 사용하기 위해서는 다운그레이드는 그리 쉽지 만을 않습니다. 왜냐하면 VS2008 에서 테스트 프로젝트를 로드하면 프로젝트의 참조가 깨져있습니다.
그 이유는 .csproj 파일을 열어보면 답이 나오는데요. 아예 Microsoft.VisualStudio.QualityTools 프레임워크의 버전 번호를 명시하여 해당 VS2008 에서는 어셈블리 리디렉션(Assembly Redirection) 을 시키기가 좀 애매해 집니다.
문제 해결 기본 지식
이 문제를 해결하기 위해서는 테스트 프로젝트간의 비 호환적인 테스트 프레임워크로 인한 문제이므로, 문제를 해결하기 위한 접근 측면에 제한을 둘 수 밖에 없습니다. Visual Studio 2010 에서는 Coded UI, Test Impact 등 새로운 기능이 추가되었고, 기존 테스트 또한 비주얼한 부분이 개선이 되면서 강제적으로 테스트 프레임워크의 버전을 .NET Framework 4 로 고정을 시키는 것 같습니다.
이 문제는 MSBuild 를 통해 해결하기 위한 기본적인 지식을 알려드립니다. 여러분이 알다시피 MSBuild 는 Microsoft 의 통합 빌드 솔루션입니다. 예전에는 "빌드"라는 말 대신 "컴파일"이라는 단어를 사용했었죠. 컴파일이란 소스 코드를 목적 파일 또는 실행 파일로 변환하는 과정을 "컴파일"이라고 합니다.
컴파일과 빌드를 비교하는 아주 간단한 그림 입니다.
컴파일은 목적은 소스 코드를 목적 파일로 변환하여 실행 파일 또는 라이브러리로 만들기 위한 목적입니다.
빌드는 컴파일의 일련의 과정을 플로우(Flow) 로 처리하여 컴파일 중에 더 많은 작업을 하기 위한 목적입니다.
가장 대표적인 빌드 솔루션이 MSBuild 이며, 이 외에 Ant 또는 NAnt 등이 바로 이러한 솔루션입니다. 그리고 Team Foundation Server 의 팀 빌드도 바로 MSBuild 에 기반하고 있다는 것입니다.
필자 또한 MSBuild 를 접하면서 나의 지식의 끝을 무한하게 확장해 주었던 것이 MSBuild 입니다. MSBuild 는 정적인 컴파일 방식에서 동적인 방식의 빌드로 거듭나면서 굉장히 많은 가능성을 보여주는 부분이기도 합니다. Microsoft 의 MSBuild 의 대략적인 구조는 아래와 같습니다.
기본적으로 MSBuild 는 Task 의 집합이라고 해도 과언이 아닙니다. 그리고 이 Task 중에 빌드와 연관된 Task 도 있습니다. 이 Task 를 .NET Framework 버전에 따라 Project References(프로젝트 참조)를 변형시키는 방법입니다.
해결 방법
이 테스트 프로젝트를 VS2008, VS2010 양 쪽에서 사용하도록 하기 위해서는 이 어셈블리 참조를 동적으로 변화시킬 필요가 있습니다. 이 방법도 MSBuild 의 Choose 라는 조건문으로 제어를 분기할 수 있는 방법입니다.
1. 먼저 솔루션 탐색기에서 열려 있는 프로젝트를 언로드 한 후, 편집을 클릭합니다.
2. 그럼 아래와 같이 참조와 관련되어 있는 부분이 ItemGroup 요소에 있는 것을 확인할 수 있습니다.
3. 이 ItemGroup 에서 VS2008, VS2010 에서 공통적인 참조 어셈블리를 별도의 ItemGroup 으로 분리합니다. 그럼 아래와 같은 형태가 되겠지요?
4. 테스트와 관련된 ItemGroup 에 Choose 조건 분기 요소를 사용하여 조금 변형해 봅시다. .NET Framework 의 버전 별로 말이죠.
위의 $(MSBuildBinPath) 는 실제로 빌드가 수행할 때의 MSBuild 의 경로를 나타냅니다. 하지만 여기에는 한 가지 함정이 있습니다. Visual Studio 2008 에서는 <Message Text="$(MSBuildBinPath)" /> 가 아래와 같이 C:\Windows\Microsoft.NET\Framework\v2.0.50727 로 나타납니다. 하지만 내부적으로 이 MSBuild 는 v3.5 경로의 MSBuild.exe 를 실행하게 됩니다. 자세한 이유와 내막은 Microsoft.Common.targets 파일을 뒤져보시면 아실거라고 생각합니다.
그리고 Choose 조건 분기 요소는 if ~ else 와 같은 구문입니다. ItemGroup 요소는 하나의 항목을 담는 필드라고 보시면 되고, PropertyGroup 은 한 Property 에 여러 항목을 담는 속성이라고 보시면 됩니다. 이 부분은 MSBuild 를 공부해 보시면 어렵지 않는 기본적인 부분이니 자세한 설명은 여기에서 하지 않겠습니다.
5. 모두 완료 되었습니다. 각각의 VS2008, VS2010 에서 테스트 프로젝트를 모두 사용할 수 있게 되었습니다.
만약 Coded UI 와 같은 VS2010 의 새로운 기능을 사용할 경우 아래와 같이 추가적인 어셈블리를 참조하게 됩니다.
이 경우도 위의 4번과 같이 ItemGroup 의 VS2010 용 어셈블리를 아래와 같이 넣어버리면 됩니다.
그럼 VS2008 인지 VS2010 인지에 따라서 참조 어셈블리가 완벽하게 분리가 됩니다.
하지만 VS2008 에서 빌드를 할 경우 아래와 같이 오류가 발생하게 됩니다. 당연히 VS2008 에서는 Coded UI 등에서 필요한 Microsoft.VisualStudio.TestTools 프레임워크가 존재하지 않고, 이 프레임워크를 재사용하기 힘들기 때문입니다.
하지만 이 문제로 해결해 볼까요? 위에서 Property 를 재정의한 구문이 생각나실 겁니다. 전처리 지시문의 상수 값으로 사용되는 <DefineConstants> 에 NET4.0 빌드인지, NET3.5 빌드인지 알 수 있도록 상수 값을 선언하였습니다.
이 상수 값을 이용하여 CodedUI 등 VS2010 에서 새로 추가된 부분에, #if ~ #endif 지시문을 사용하여 감싸 주시면 됩니다.
이제 Visual Studio 2008 이든 Visual Studio 2010 이든 테스트 프로젝트를 양 쪽 어떤 도구를 사용하든 테스트가 가능하도록 구성하는 방법을 완료하였습니다.
지금까지는 닷넷 4.0에 추가된 TPL과 PLINQ를 통해서 멀티 스레드 프로그래밍을 하는 방법을 살펴봤습니다. 그러면, 잠깐 추억을 되살릴겸, 뭐가 어떻게 달라졌는지도 한번 비교해 볼겸 해서, 닷넷 3.5까지의 멀티 스레드 프로그래밍 방법을 잠깐 살펴보도록 하겠습니다. 호호호호
- Thread와 다이다이로 작업하던 시절.
TPL은 System.Threading.Tasks를 사용해서, ThreadPool을 내부적으로 사용한다고 말씀을 드렸었습니다. 하지만, 그것 닷넷 4.0이나, 닷넷 3.5에서는 Reactive Extension(Rx)을 통해서 추가적으로 지원하는 기능이구요. 그 이전에는 직접적으로 Thread나 ThreadPool을 이용해서 프로그래밍 해야 했습니다. 그럼 Thread를 직접 사용하던 코드를 예제로 한번 보시죠.
using System; using System.Threading;
namespace Exam18 { class Program { static readonly int max = 10000;
//현재 작업중인 스레드에서도 반복문 시작 for (int count = 0; count < max; count++) { Console.Write("-"); } Console.WriteLine("메인 스레드 끝");
//혹시 현재 스레드가 빨리 끝나더라도, //추가 스레드가 끝날 때 까지 기다리기. thread.Join(); } } }
<코드1> Thread와 다이다이로.
<코드1>을 보면, 맨 처음에 Task를 소개해드리면서 사용했던 예제를 Thread를 사용하도록 바꾼 코드입니다. 차이점이 있다면, ThreadStart타입의 델리게이트를 사용해야 한다는 것과, Wait()메서드가 아니라 Join()메서드를 사용한다는 것이죠. 결과를 보시면, Task를 사용했던 것과 동일합니다.
---------|||||||-|||||||||||--------------|||||||||-------------|||||------|||||||||||--------- -||||||||--------|||||||||||||-----메인 스레드 끝|||||||||||||||||||||||||||||||||||||||||||||| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| |||||||||||||||||||||||||||||||||||||||||||||||||||추가 스레드 끝 계속하려면 아무 키나 누르십시오 . . .
<결과1> Thread를 사용한 결과.
그리고 Thread를 보면, Task와 마찬가지로 실행을 제어할 수 있도록 몇가지 속성을 제공하는데요, 그 목록을 보면 아래와 같습니다.
속성
설명
Join()
추가 스레드가 완료되기 전에 메인 스레드가 완료되면, 추가 스레드가 하던 작업은 다 날아간다. 그래서 추가 스레드의 작업이 완료될 때까지 메인 스레드가 기다리도록 한다.
IsBackground
이 속성은 기본적으로 false이다. 즉, 스레드는 기본적으로 foreground작업인데, 그 때문에 스레드가 완료되기 전까지는 프로세스를 종료시킬 수 없다. 이 속성을 true로 주면, 스레드의 작업이 완료되기 전에도 프로세스를 종료시킬 수 있다.
Priority
Join메서드를 사용한 경우에, 이 속성을 통해서 스레드의 우선순위를 바꿀 수 있다.
ThreadState
이 속성을 통해서 스레드의 상태를 파악할 수 있는데, Aborted, AbortRequested, Background, Runnging, Stopped, StopRequested, Suspended, SuspendRequested, Unstarted, WaitSleepJoin등의 상태 값을 얻을 수 있다.
Thread.Sleep()
현재 실행 중인 스레드의 실행을 명시한 시간만큼 일시정시 시키는 메서드이다.
Abort()
이름 그대로, 스레드를 중지시키는 메서드. ThreadAbortException이 발생된다.
<표1> Thread의 속성.
위의 Thread멤버 중에서, Task에도 있는 건, Join()과 ThreadState뿐입니다. 왜 그럴까요? 일반적으로 권장되지 않는 것들이기 때문이죠. 그래서 닷넷 프레임워크 4.0으로 프로그래밍 할 때는, 위에서 언급한 것들 중에서 Task에 없는 속성들을 될 수 있으면 사용하지 말아야 합니다.
- ThreadPool을 사용해보자.
ThreadPool을 사용하면, 새로운 스레드를 계속 해서 생성하기 보다 기존에 있는 스레드를 재활용해서 추가적인 스레드 생성을 막을 수 있습니다. 참고로, TPL이 내부적으로 ThreadPool을 사용한다고 말씀드렸었죠? 그럼 ThreadPool을 사용하는 예제도 한번 보시죠.
using System; using System.Threading;
namespace Exam19 { class Program { static readonly int max = 10000;
//혹시 현재 스레드가 빨리 끝나더라도, //추가 스레드가 끝날 때 까지 기다리기. Thread.Sleep(1000); } } }
<코드2> ThreadPool을 사용한 코드.
<코드2>를 보시면, ThreadPool을 사용하고 있는데요. QueueUserWorkItem메서드를 통해서 작업을 추가하고 있습니다. 그러면, 자동으로 스레드를 활용해서 작업을 시작하게 되구요. 결과는 앞선 예제와 동일합니다. 그런데, ThreadPool을 사용할 때 장점만이 있는 건 아닌데요. 작성한 코드외에도 다른 라이브러리등에서 내부적으로 시간이 많이 걸리는 I/O작업 등에 ThreadPool을 사용한다면, 그 작업이 끝날 때까지 기다려야 하거나, 심한 경우에는 데드락이 발생하기도 합니다. 그리고 Thread나 Task를 사용할 때와는 다르게 ThreadPool은 실행 중인 작업에 접근할 수 있는 방법이 없습니다. 그래서 실행 중인 작업을 조종한다거나, 상태를 확인할 수 가 없죠. 그래서 <코드2>를 보시면, Join()이나 Wait()대신에, Thread.Sleep()메서드를 통해서 추가 스레드가 끝날 때까지 메인 스레드를 기다리게 합니다.
- 마치면서
오늘은 닷넷 3.5 까지의 멀티 스레드 프로그래밍 방법에 대해서 알아봤는데요. 크게 다른 모습은 없습니다. 다만, 좀 더 안전하고 간단한 방법을 제공하는 것이죠. 대한민국도 16강에 진출했는데 오늘은 여기까지 하시죠!...응??
- 참고자료
1. Essential C# 4.0, Mark Michaelis, Addison Wesley
네. 아직입니다. ^^; 원래는 마무리를 지으려고 했었는데요. 갑자기 jQuery 가 급땡기는 바람에 슬슬 관련글을 적어보렵니다.
클라이언트단에서 유효성검사하기
지난번 포스팅을 보시면, 서버단의 모델 클래스에 DataAnnotaion을 사용하여 유효성검사를 했습니다. 물론, 클라이언트단에서도 자바스크립트를 사용하여 유효성검사를 할 수 있지만, 이는 동일한 유효성 검사를 두번(서버와 클라이언트) 하게됩니다. DRY(Don't Repeat Yourself) 규칙에 위반되는 작업인 거죠.
근데 왜?
저 아시는 분 없죠? 듣보잡인거죠. 그래서 이렇게 앞뒤가 없습니다. 이번 포스팅을 먼저 했으면 하는 마음도 있지만, 뭐 이렇게 된 것 그냥 적어내려갑니다.^^
DRY에 반하는 작업을 한다고 너무 차가운 피드백은 달지 말아주세요; '이런 방법도 있는 거였군'이라는 생각만 가져주셨으면 좋겠습니다.
먼저, 지난번 유효성 검사를 했던 소스에 jQuery를 이용한 유효성 검사 스크립트를 추가하겠습니다.
프로젝트내의 Scripts폴더에 있는 jquery-1.4.1.js와 jquery.validate.js파일을 추가합니다.(프로젝트에 이런 스크립트 파일들이 자동으로 적용되어있는 것으로 봐서는 맘껏 사용하라는 거겠죠?^^; 아. 그리고 미니버전을 사용해도 되는 것은 다들 아시죠? *min.js)
소스를 보시면(빨간색) jQuery 스크립트와 유효성 검사를 위한 스크립트를 추가하였습니다. 또한 유효성 검사를 담당하는 jQuery 스크립트 구문도 추가하였습니다.
자, $("form").validate() 를 통해 유효성 검사를 합니다. 보시는대로, rules와 messages를 통해 에러를 표시하게되죠. Email을 제외한 각 필드를 필수값으로 세팅을 했고( required: true), 이름은 5자 이내(maxlength :5), 단축다이얼은 1~99까지의 숫자를 받도록(range[1,99]), 이메일은 이메일 형식을 체크(email: true)하도록 하였습니다. 이밖의 옵션들은 여기서 확인하실 수 있습니다.
빈값으로 폼을 전송하려고하면 클라이언트단에서 이에 제재를 가하게 됩니다.
이메일(필수값 아님)을 제외한 나머지는 에러가 났습니다. 올바른 값을 하나하나 입력하면 바로바로 에러메시지가 사라지는 것을 확인할 수 있습니다.
이메일을 잘못입력하면 에러메시지가 뜨는 것도 확인할 수 있습니다.
여기까지 잘 따라오셨으면 보다 싶게 클라이언트단에서의 유효성 검사를 진행해보죠. (윗부분은 이제 잊어도 좋습니다. 딱히 잊으라는게 아닌 아래 소스에서는 필요가 없어서.. 이렇게 말씀드리는건데...음.. '아 이런방법도 있구나'만 기억하시면 됩니다.^^;;)
DRY 잊지말자
지난번 포스팅에서는 서버단에서 유효성 검사를 하였기때문에 유효성 에러 메시지를 보려면 서버단까지 다녀와야할 필요가 있었습니다. 이를 가만히둘 마이크로소프트가 아닙니다. 정말 심플한 방법으로 손쉽게 클라이언트단과 서버단 두군데 모두 유효성검사를 할 수 있도록 하는 단 세줄의 코드가 있습니다.
위 세줄을 추가함으로 지난번 포스팅에서 DataAnnotation을 이용한 유효성 검사 로직을 클라이언트단에서도 사용할수 있게 되었습니다. 실행을 시킨 후, Create 버튼을 클릭하면 리로드없이 즉각적으로 에러메시지를 확인할 수 있습니다.
또한, 유효한 값을 입력하면 즉시 에러메시지가 사라집니다.
저희는 지금 클라이언트단에 유효성 검사 로직을 추가하지 않았습니다. 유효성 검사로직은 모델클래스에만 존재하고 있습니다. 하하하.(승리자의 웃음인거죠^^) 룰은 한 곳에다가 두고, 두군데(클라이언트와 서버)에서 모두 검사를 하도록 하였습니다. 이로써 DRY를 잊지 않은체 작업이 완료되었습니다.
마무리요
이렇게 손쉽게 클라이언트단에서도 검사가 가능한 방법이 있었습니다. ㅎㅎ 기분좋네요.
마이크로소프트는 현재 jQuery 프로젝트에 참여하여 계속 플러그인을 개발중에 있습니다. (이 얘기는 왜하는 걸까요? 음..) 이 부분에 대해서도 포스팅을 하도록 노력해보겠습니다.
Asynchronous Agents Library
– message 전달 함수. 1 ( 전송 )
작성자: 임준환( mumbi at daum dot net )
Message 메커니즘
Asynchronous Agents Library( 이하, AAL ) 에서 message 메커니즘이란 agent 들 간의 데이터 교환이나 동기화 등 상호 작용을 위해 사용되는 기능입니다.
Message 메커니즘은 크게 message 를 보내고( send ) 받는( receive ) 전달 함수( passing function )와 message 들을 관리하거나 message 에 특별한 기능을 부여하는 message block 으로 구성되어 있습니다.
실질적으로 message block 이 다루는 message 란 빌트인 자료 형( built-in type )이나 클래스와 같은 사용자 정의 자료 형( user define type ) 의 데이터입니다.
이 글에서는 먼저 message 를 주고 받는 전달 함수 중 전송 함수에 대해서 알아보겠습니다.
Message 전송
Message 전달 함수 중 보내는 기능을 하는 함수에는 동기 전송 함수 send() 와 비 동기 전송 함수 asend(), 이렇게 두 가지가 있습니다.
동기와 비 동기라는 용어가 혼란스러울 수 있기 때문에 잠깐 언급하고 넘어가겠습니다.
여기서 쓰이는 동기( synchronous )라는 용어는 병렬 처리에서 쓰이는 동기화( synchronization )라는 용어와는 약간은 다른 개념입니다.
동기화는 다른 두 시간을 하나로 일치시킨다는 뜻으로 행위를 말합니다. 반면 동기는 이미 동기화되었다는 뜻으로 상태를 뜻합니다. 마찬가지로 비 동기는 동기화되지 않았다는 뜻입니다.
즉, 보통 프로그래밍에서 동기 함수란 그 함수가 호출되고, 그 함수가 반환될 때까지 해당 컨텍스트가 진행되지 않고 기다리다가 반환되고 나서야 컨텍스트가 진행되는 함수를 말합니다. 이것은 사실 컨텍스트가 기다리는 것이 아니라, 해당 컨텍스트가 함수의 내용을 직접 처리하기 때문에 함수를 호출한 입장에서 보면 기다리는 것처럼 보이는 것입니다.
마찬가지로 비 동기 함수는 함수를 호출한 컨텍스트가 직접 함수의 내용을 처리하지 않고 새로운 작업 스레드를 생성하고 생성된 스레드의 컨텍스트가 진행되기 때문에 함수를 호출한 컨텍스트는 함수를 호출하자마자 함수의 반환을 받고, 계속해서 진행되는 것입니다. 함수를 호출한 컨텍스트는 이러한 비 동기 함수가 언제 실제로 종료될지 모르기 때문에 함수의 반환이 아닌 다른 기법이 필요합니다. 보통 폴링( polling )이나 메시지 또는 콜백 함수와 같은 기법을 사용하여 함수의 종료를 알 수 있습니다.
그럼 이제 본격적으로 두 message 전달 함수에 대해서 알아 보겠습니다.
동기 전송 함수 send()
앞에서 설명한 것처럼 send() 는 동기 함수이기 때문에 message 가 전송에 대한 결과가 확실해 졌을 때 반환됩니다. 즉, 전송된 결과가 확실할 때까지 기다린다는 뜻입니다.
#include
using namespace Concurrency;
int main()
{
// message block
unbounded_buffer< int > message_block;
asend( message_block, 1 );
Concurrency::wait( 10 );
}
[ 코드4. asend() 예제 ]
asend() 가 반환되었을 때에는 아직 message block 에 message 가 전송되지 않았습니다. 이것으로 asend() 가 비 동기 함수임을 확인할 수 있습니다.
약간의 시간( 10 milli second ) 이 지난 후에는 message block 에 message 가 전송된 것을 확인할 수 있습니다.
[ 그림2. asend() 예제 디버깅 화면 - 호출 직 후 ]
[ 그림3. asend() 예제 디버깅 화면 - 약간의 시간이 지난 후 ]
Message 필터
Message 전송 함수인 send() 의 반환 값이 전송 결과라고 하였고, 실패할 경우 false 를 반환한다고 하였습니다. 사실, 실패할 경우란 message 를 받는 message block 이 전송을 거부할 경우, 즉 필터링되었을 경우입니다.
결론적으로 send() 와 asend() 의 반환 값은 모두 message block 의 수락 또는 거절 여부입니다.
여기서 집고 넘어가야 할 부분이 언제 전송이 거부되는 것인가 하는 것입니다.
message block 은 두 가지 경우에 message 전송을 거부합니다.
첫째는 message block 이 파괴되어 소멸자가 처리되고 있을 때입니다. 당연한 상황입니다.
둘째는 message block 의 필터에 의해 message 가 거부당했을 때입니다. 모든 message block 의 생성자 중에는 filter_method 형의 매개변수를 갖는 생성자가 있습니다. filter_method 형은 사실 std::tr1::function<bool(_Type const&)> 입니다. message block 을 생성하는 클라이언트는 임의의 message 필터를 적용할 수 있습니다. 이 필터 함수가 false 를 반환할 경우, message 전송은 거부됩니다.
이 예제에는 message block 의 필터로 Visual studio 2010 에서 지원하는 C++0x 의 람다를 사용하였습니다. 람다는 이 글의 논제에서 벗어나기 때문에 설명하지 않도록 하겠습니다. Visual studio 팀 블로그에서 람다에 대한 정보를 얻을 수 있습니다.
간단히 람다에 대해서 설명하고 넘어가자면 익명의 함수 객체라고 보셔도 될 것입니다.
예제에 사용된 message block 의 필터는 짝수만 수락하는 필터입니다. 그래서 실행 결과로 send() 와 asend() 모두 홀수는 거부되었고, 짝수는 수락되는 것을 볼 수 있습니다.
[ 그림4. 전송 거부 예제 실행 결과 ]
마치는 글
이 글에서는 message 전달 함수 중 전송 함수들에 대해서 알아보았습니다.
이 함수들 중 어떤 것을 사용하는 것이 적절한지를 판단하기 위해서는 반드시 동기와 비 동기에 대한 개념의 이해가 필요합니다.
상황에 따라 적절한 함수를 사용하시면 원하는 결과를 얻을 수 있을 것입니다.
전송 함수들에 대해서 알아보았지만 아직 수신 함수들에 대해 알아보지 않았습니다. 다음 글에서는 message block 으로부터 message 를 수신하는 수신 함수들에 대해 알아보겠습니다.
6월 1일 REMIX10 행사를 기점으로 Visual Studio 2010 한글판이 대중에 공개가 되었습니다. Visual Studio 2010 의 영문 버전은 그 이전에 출시가 되었지만 한글판이 출시된 이후에 더 많은 관심을 받게 되었습니다.
Visual Studio 2010 으로 개발 환경을 업그레이드를 진행하는 곳이 특히 해외에서 많습니다. 제가 그걸 어떻게 다 아냐구요? 항상 트위터 검색을 통해 해외에서 Visual Studio 2010 를 어떻게 사용하고 있는지 매일 매일 관심 있게 보고 있답니다. ^^
어쨌든 Visual Studio 2008 을 쓰고 있지만, Microsoft MVP 이거나 회사에서 MSDN Subscription 라이선스가 있다면 Visual Studio 2008, Visual Studio 2010 개발 도구가 혼합해서 사용될 경우가 있습니다. 이런 경우 두 개발 도구에서 쌍방 개발 가능하게 구성을 할 수 있습니다.
이 방법은 제니퍼소프트의 정성태 과장님의 블로그에서 예전에 소개했던 VS2005, VS2008 혼합해서 사용하는 방법과 동일합니다.
1. 간단한 예제로 Console Application 을 Visual Studio 2008 에서 생성했습니다.
2. 기존의 솔루션 파일의 복사본을 하나 만듭니다.
3. 솔루션 파일을 노트패드로 ConsoleApplication - VS2010.sln 파일을 열어 다음의 항목을 수정합니다.
4. 프로젝트 파일을 열어 ToolsVersion 의 '3.5' 를 '4.0' 으로 수정합니다. 'ConsoleApplication1.csproj'
만약 다수의 프로젝트일 경우, 위의 3번에서 수정한 솔루션 파일을 열면 프로젝트를 변환하는 마법사로 진행하면 쉽게 변경이 됩니다.
5. 모두 완료 되었습니다. Visual Studio 2010 와 Visual Studio 2008 에서 각각의 솔루션 파일로 동시에 작업을 할 수 있습니다.
위의 방법을 이용하여 Visual Studio 2008 과 Visual Studio 2010 에도 모두 개발이 가능합니다. 하지만 만약 테스트 프로젝트가 포함이 되어 있다면 두 개발 도구에서 사용할 수 없습니다. 왜냐하면 .NET Framework 4.0 에서는 테스트와 관련된 Microsoft.VisualStudio.Quality 프레임워크가 개선되고, Microsoft.VisualStudio.TestTools 프레임워크가 추가되면서 이전 Visual Studio 2008 과 프로젝트가 호환이 되지 않습니다.
하지만 불가능할 것 같은 테스트 프로젝트도 Visual Studio 2008과 Visual Studio 2010에서 동시에 사용할 수 있는 방법이 있습니다. 이것은 다음에 알아보도록 하겠습니다.
현재 VS 2008로 된 프로젝트가 많고 테스트는 주로 NUnit으로 하고 있어서 도움되는 팁인 것 같습니다
그런데 VS 2010 한글판으로 Silverlight 4 개발을 할 수 있는지 궁금해요. 영문판에서 설치하고 업그레이드 하면 된다는 얘기는 들었는데 정식으로 쓸 수 있는 방법이 있는지... SL 4 개발만 잘 되면 VS 2010 한글판으로 전반적으로 업그레이드 하면 될 것 같은데 말입니다.
저는 늘 의문을 품어왔습니다...는 훼이크고 이번 포스트를 준비하면서 의문을 가지게 되었습니다. 분명 병렬 프로그래밍의 정신은 남아도는 코어를 활용해서 협력을 해서 작업을 좀 더 빨리 끝내자고 하는 건데요, 그런면에서 '백지장도 맞들면 낫다'는 말은 병렬 프로그래밍의 정신을 잘 표현하는 선조들의 지혜라고 볼 수 있습니다. 그런데요.... 과연 백지장같이 갓난 아기도 혼자들 수 있는 걸 같이 드는게 과연 나은 일일까요? 오히려 혼자 할 때보다 못한 결과를 가져오지는 않을까요? 오늘은 그에 대한 이야기입니다.
- LINQ도 맞들면 낫다, 어헣.
LINQ는 데이터 쿼리에 가까운 표현을 사용하면서, 데이터 쿼리시에 직관적이고 선언적인 코드를 활용할 수 있도록 해주었는데요. 거기에 이전 포스트들에서 설명드렸던 Parallel.For나 Parallel.ForEach처럼 매우 간단하게 남아도는 코어를 활용할 수 있도록 하는 방법을 제공합니다.
using System; using System.Linq; using System.Threading.Tasks; using System.Threading;
namespace Exam15 { class Program { static void Main(string[] args) { int[] nums = Enumerable.Range(1, 10000).ToArray();
<코드1>을 보시면, 1부터 1000까지의 숫자를 가진 배열을 생성하고, 각 수를 제곱한 수를 구하는 코드입니다. 기존의 LINQ코드와 다른 점이 있다면, 제곱 연산을 수행하기 위한 데이터 소스인 nums에 대해서 AsParallel()을 호출했다는 것입니다. <코드1>에선 AsParallel()의 리턴타입이 ParallelQuery<int>인데요, LINQ에서는 Enumerable을 사용하지만, PLINQ에서는 ParallelEnumerable을 사용합니다.
<코드1>을 보면, 정말 간단하게 병렬 프로그래밍이 구현되는데요. 정말 저렇게 간단한 방법으로 병렬 쿼리가 실행되는지 확인하기 위해서 Task.CurrentId를 통해서 실행중인 스레드의 Id를 출력하도록 했습니다. 그리고 비교적 일관성 있는 결과를 얻기 위해서 Thread.Sleep를 통해서 실행을 조금 여유롭게 해줬죠. 결과를 보실까요?
3->1->4->2의 패턴이 반복되는 걸 확인하실 수 있습니다. 물론, 실행도중에 패턴은 바뀌기도 합니다만, 분명 AsParallel()메서드를 호출하는 것 만으로도 병렬 프로그래밍이 구현된 것이죠. 그런데, 출력되는 스레드의 아이디를 보면, 딱 4개만 생성된 걸 확인할 수 있는데요. 제 컴퓨터의 CPU가 쿼드코어라서 딱 4개만 생성된 것 같습니다. 그런데 왜 딱 4개만 생성된 걸까요? 이전에 TPL을 활용해서 작업할 때는 4개 이상의 스레드도 생성되어서 작업을 처리했는데 말이죠. 그건 PLINQ가 병렬 쿼리를 처리하는 방식에서 원인을 찾을 수 있습니다.
제가 술을 먹고 만취한 상태에서 글을 적어서 그럴까요? 아래 내용은 새빨간 거짓말 입니다!!! 낄낄낄-_-;;; 스레드가 4개만 생성된 건, PLINQ가 분할 알고리즘으로 구간 분할을 사용하기 때문에 그렇습니다. 그리고 정확한 설명은, PLINQ는 ParallelEnumerable타입 같이 병렬 쿼리를 돌려도 안전한 타입에 대해서는 주저없이 쿼리를 병렬화 해서 작업을 하지만, IEnumerable타입 같이 병렬로 쿼리를 돌릴 때, 안전하다고 보장할 수 없는 경우에는 순차적인 쿼리로(정확히 말하지만, 순차적인 쿼리가 아니라 Chunk 분할 알고리즘을 통해서 데이터 소스에 락을 걸고, 스레드가 한번에 작업할 덩어리를 떼어주는 형태로)작업을 하게 됩니다. 오해 없으시길 바랍니다! 어헣-_-;;;
PLINQ는 AsParallel()메서드로 데이터 소스에 대해서 병렬처리를 원했다고 하더라도 항상 병렬적으로 처리를 하지는 않습니다. 예를 들면, 작업이 너무나 간단해서, 병렬적으로 처리할 때 오히려 손해를 보는경우가 있습니다. 작업이 너무 간단하기 때문에 각 스레드가 처리하는 작업의 시간이 매우 짧고, 그래서 작업 처리에 걸리는 시간보다, 스레드 간의 작업전환에 더 많은 시간이 걸리는 것이죠. 그래서 PLINQ는 AsParallel()이 호출되면, 우선 쿼리를 분석합니다. 그리고 그 쿼리가 간단하다는 판단을 하면, 병렬적으로 처리하기 보다는 순차적으로 처리를 하게 되는 것이죠. <결과1>에서 스레드가 4개가 돌아간 것은, CPU의 코어가 4개 이기 때문에, 코어별로 스레드가 한 개씩 생성된 것입니다. 각 코어의 입장에서 보자면, 스레드가 한 개씩 있는 셈이므로 작업전환이 필요없겠죠. 참고로, 듀얼 코어인 제 노트북에서 실행한 결과는 아래와 같습니다.
패턴은 약간 불안정할 때도 있지만, 대략 1->2->3의 순서를 유지하고 있습니다. 그런데, 왜 이렇게 스레드의 개수를 정해 줄 수도 있게 했을까요? 바로 최적화 때문입니다. 기본적으로 PLINQ의 알고리즘은 많은 경우를 테스트해서 최적화 알고리즘을 만들어 놓았기 때문에, 대부분의 경우는 기본옵션으로 실행하는 것이 가장 좋은 결과를 냅니다. 하지만, 그렇지 못한 경우가 있을 수 있는데요. 그럴 때, 테스트를 통해서 적절한 스레드 개수를 지정할 수 있도록 옵션을 둔 것이죠.
위에서 쿼리 식이 단순하면, 순차적으로 실행한다고 말씀을 드렸는데요, 쿼리 식이 병렬로 실행하기에 안전하지 못한 경우에, 순차적으로 실행하다고 말씀을 드렸는데요, 그런 경우도 병렬적으로 실행을 강제할 수 있습니다. 쿼리 식에 '.WithExecutionMode(ParallelExecutionMode.ForceParallelism)'메서드를 추가하면, 기본 알고리즘과는 상관없이 무조건 병렬적으로 실행하도록 합니다. 실행시간을 테스트한다거나 할때 유용하게 사용할 수 있는 옵션이겠죠.
- LINQ 맞들기 취소는 어떠케?
이번에는 PLINQ 쿼리를 취소하는 방법에 대해서 알아보겠습니다. 지금까지 취소에는 CancellationTokenSource를 활용했었죠? 마찬가지 입니다. 똑같이 Token을 활용해서 취소에 사용하되, 사용하는 방법이 조금씩 다른 것 뿐이지요.
using System; using System.Linq; using System.Threading; using System.Threading.Tasks;
namespace Exam17 { class Program { public static int[] SimpleParallelTask(int[] source, CancellationToken token) { Func<int, int> square = (num) => { Console.WriteLine(Task.CurrentId); Thread.Sleep(10); return num * num; }; return source.AsParallel() .WithCancellation(token) .WithDegreeOfParallelism(3) .Select(square) .ToArray(); }
static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource();
<코드3>을 보면, AsParallel메서드의 결과로 리턴되는 ParallelQuery타입에 포함된 .WithCancellation메서드를 사용해서 PLINQ 쿼리에 CancellationToken을 넘겨준다는 것을 제외하고는 Parallel.For, Parallel.ForEach와 동일한 방법을 사용하고 있습니다. 결과도 예측할 수 있듯이 동일합니다.
(생략) 1 3 2 1 3 2 1 3 2 1 3 2 1 3 2 1 3
------------------------------------- 쿼리가 중간에 취소되었습니다. 계속하려면 아무 키나 누르십시오 . . .
<결과4> LINQ 맞들기를 취소한 결과.
- 마치면서
어떠셨나요? '백지장도 맞들면 낫다'는 속담이 PLINQ에서는 항상 참이 아니라는 게 말이죠. 이래서 병렬 프로그래밍이 어려운가 봅니다. 어허허허헣. 악플 사절(죽을때 까지 미워할거임)! 피드백 환영! 호호호호^^
- 참고자료
1. Essential C# 4.0, Mark Michaelis, Addison Wesley
약속은 하는 것 보다, 취소하는 게 어렵습니다. 게다가 취소할 때는 적절한 타이밍을 놓치면, 안 좋은 기억만 남기게 되죠. 그래서 프로그램에서도 취소를 제대로 할 수 있도록 지원하는 게 중요합니다. 누구나 실수 할 수 있거든요.
- TPL과 함께 취소 좀 더 쉽게하기. 어헣.
TPL은 두가지 형태의 병렬성을 지원합니다. 첫 번째는 작업 병렬성(Task Parallel)이고, 두 번째는 데이터 병렬성(Data Parallelism)입니다. 작업 병렬성은 하나 이상의 작업이 동시에 진행되는 것을 말하구요, 데이터 병렬성은 연속적인 데이터에 대해서 동일한 작업이 동시적으로 수행되는 것을 말합니다. 기존까지 Task클래스와 관련해서 살펴봤던게 작업 병렬성을 위한 것이었다면, 이번에는 데이터 병렬성을 지원하는 부분을 살펴보겠습니다.
데이터 병렬성을 매우 손쉽게 지원하기 위해서 System.Threading.Tasks.Parallel클래스에 병렬성을 지원하는 for와 foreach를 추가했습니다. Parallel.For와 Parallel.ForEach가 바로 그 것인데요. 하나씩 살펴보겠습니다.
using System; using System.Linq; using System.Threading.Tasks;
for (int i = 0; i < 10; i++) { Console.WriteLine(nums[i].ToString()); } } } }
<코드1> 간단한 병렬 예제.
<코드1>을 보면, 1부터 1000까지의 정수 배열을 만든 뒤에, 각각의 수를 제곱하는 코드입니다. i번째의 숫자를 제곱해서 그 결과를 i번째 인덱스에 넣는 작업과, i+1번째의 숫자를 제곱해서 그 결과를 i+1번째 인덱스에 넣는 작업은 별개의 작업이며, 동시에 수행가능한 작업이죠. 저렇게 for와 거의 비슷한 모양으로 작성하고, for대신에 Parallel.For를 써주는 것 만으로도 남아도는 CPU의 코어를 활용할 수 있다니. 간편하죠?
Parallel.ForEach와 병렬 루프에서 예외를 처리하는 부분은 이미 다룬 부분이기 때문에 건너뛰구영. 바로, 병렬 루프를 취소하는 방법에 대해서 알아보겠습니다. 지난 포스트에서 작업을 취소하는 방법에 대해서 알아봤었는데요. 이번에도 크게 다르지 않습니다. 동일하게 CancellationTokenSource와 CancellationToken클래스를 활용합니다. 다만, 방법이 약간 다른데요, 예제를 보시죠.
using System; using System.Collections.Generic; using System.Linq; using System.IO; using System.Threading.Tasks; using System.Threading;
namespace Exam14 { class Program { static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource(); ParallelOptions parallelOptions = new ParallelOptions { CancellationToken = cts.Token }; cts.Token.Register( () => Console.WriteLine("Cancelling....") );
cts.Cancel(); task.Wait(); foreach (var file in fileList) { Console.WriteLine(file); } Console.WriteLine("총 파일 개수 : {0}",fileList.Count()); } } }
<코드2> 병렬 루프에서의 작업 취소.
<코드2>를 보면, Parallel.ForEach이용해서, 음악 파일 중에서 10메가가 넘는 파일만 찾아서 리스트에 담고 있습니다. 그리고 루프 취소와 모니터링을 위해서 CancellationTokenSource, CancellationToken클래스를 활용하고 있습니다. 다른점이 있다면, 병렬 루프에 옵션을 주기 위해서 ParallelOptions클래스를 사용하고 있다는 것이죠. 그리고 생성된 ParallelOptions타입의 객체에 Token을 주고, 그 객체를 Parallel.ForEach루프에 매개변수로 넘겨주고 있습니다. 결과를 보면, 늦게 취소를 한 경우에는 리스트가 모두 완성된 반면에, 빨리 취소를 한 경우에는 리스트가 만들어지다가 만 걸 확인할 수 있죠.
끝내려면 엔터키를 누르세용. 파일 개수 : 2746
Cancelling....
(중략...)
05 サンクチュアリ.mp3 06 空のように 海のように.mp3 07 月の虹.mp3 총 파일 개수 : 380 계속하려면 아무 키나 누르십시오 . . .
<결과1> 늦게 취소해서 다 완성된 리스트.
끝내려면 엔터키를 누르세용. 파일 개수 : 2746
Cancelling....
(중략...)
01.mp3 02.mp3 03.mp3 01.うるわしきひと.mp3 총 파일 개수 : 256 계속하려면 아무 키나 누르십시오 . . .
<결과2> 중간에 취소해서 만들어지다 만 리스트.
ParallelOptions를 통해서 CancellationToken을 받은 병렬 루프는 내부적으로, IsCancellationRequested속성을 계속해서 주시하고 있습니다. 그리고, 이 취소 요청이 들어와서 이 속성이 true가 되면, 그 이후로는 새로운 루프가 시작되는 걸 막아버리는 것이죠. 그리고 병렬 루프가 취소되었음을 외부에 알릴 수 있는 유일한 방법이 OperationCanceledException을 통해서 인데요. <코드2>를 보면, catch를 통해서 예외를 잡긴하지만, 무시해버렸습니다. 그래서 Register메서드를 통해서 등록된 "Cancelling...."이라는 메세지가 출력되고 프로그램이 종료된 것이죠.
- 마치면서
역시 병렬처리를 간단하게 만들어 주는 만큼, 병렬처리를 취소하는 방법도 최대한 간단하게 만들어 주네요. TPL만쉐이! 어헣.
댓글을 달아 주세요