In to the C# 2015. 1. 29. 13:16

C# 6.0 New Features

조만간 릴리즈될 C# 6.0 (현재 Preview 공개)의 new feature들을 보니, 그동안 불편했던 부분이 많이 개선이 되고 있음을 느낄 수 있었습니다.

 

재밌고 유용한 내용들을 모아보았습니다.

 

Static Using Syntax

기존에 using 지시자는 namespace에 사용할 수 있었습니다. 이제는 static 클래스에도 사용이 가능해졌습니다.

 

예를 들어 System 네임스페이스에 있는 static class Console에 using을 사용하면, 코드에서 Console.WriteLine() 대신 WriteLine()를 사용할 수 있습니다.

 

물론, 커스텀 클래스에도 적용이 가능합니다.

 

https://dotnetfiddle.net/tYMyBB

 

  

Auto Property Initializers

가장 마음에 드는 기능 중에 하나입니다. 기존에는 프로퍼티를 선언과 동시에 초기화할 수가 없어서, 생성자에서 초기화를 하였습니다.

 

하지만 이제는 생성과 동시에 초기화를 할 수가 있게 되었습니다.

 

https://dotnetfiddle.net/2hDVTJ

 

 

 

Dictionary Initailizers

Dictionary의 초기화 방법이 추가되었습니다. 기존 방식은 아래와 같은데, 초기 아이템이 많아질 수록, key 혹은 value가 클래스 타입일 수록 코드가 복잡했습니다.

 

새로 추가된 초기화 방법은 다음과 같은데, 가독성이 좀 더 나아진 느낌입니다.

 

https://dotnetfiddle.net/23VUxF

 

 

 

Null Condition Operator

null 체크 오퍼레이터가 추가되어 if (obj == null)과 같은 코드를 줄일 수가 있습니다.

 

 

 

Expression Bodied Functions and Properties

Expresson Bodied Functions과 Properties가 새로 추가되었습니다.

 

 

 

'In to the C#' 카테고리의 다른 글

[C#] REST API 만들기  (0) 2014.02.13
[C#] Task의 작업완료  (2) 2014.01.26
[C#] Action, Func 그리고 Task  (0) 2014.01.26
[C#] Attribute : 속성  (0) 2014.01.24
Lazy Initialization  (0) 2014.01.20
In to the C# 2014. 2. 13. 00:48

[C#] REST API 만들기

참고 : http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api


갑자기 REST 를 게임서버에서 하시는 분은 없겠죠... 

이미 여러곳에서 쓰는걸로 알고 있고, 특히 EVE 온라인에서는 외부에 API 를 공개해 유저들이 다양한 정보를 활용, 앱 같은걸 만들 수 있게 사용하고 있습니다.
( eve online. CREST document : https://wiki.eveonline.com/en/wiki/CREST_Documentation )


REST 는 여러 분야에서 사용하고 있으니 Pass...


저는 이걸 어따 쓸려고 하면.. 서버 패치, 실행, 관리 등에 사용할려고 작업중입니다. 

각 Agent 의 프로토콜을 REST로 해놓으면 가져다 붙이 좋을꺼라는 생각에..


REST를 구현 하기 위해 Node.js 를 사용했었는데 단점이 있습니다.

  1. window api 에 대한 접근이 어려움

  2. process 를 관리하기가 힘듬

  3. 사용하는 패키지 중에 build 라도 해야 한다면 각 서버마다 build 해야함

어려운건 아니지만 컴파일러를 설치해야 합니다. 적어도 0MQ...

  4. process 를 자식으로 생성해서 관리하는데 버그가 있음 

평소에는 멀쩡하다 패킷이 몰렸더니 데드락.. detach 하고 node를 죽였더니 풀림...


구현은 쉬운데, 아쉬운점이 많아서 아래 포스팅(self-hosting)을 보고 C#으로

서론은 이정도 하고 간단하게 구현해 보겠습니다.

  

1. 프로젝트 생성시 ASP.NET 웹 응용프로그램으로 생성하고, 옵션에서 web api를 선택합니다.



2. 요청시 보여주고 싶은 데이터를 Model 폴더에 class 로 생성합니다. 

namespace 때문에 굳이 model 폴더에...


3.웹에서 접근할 API 를 만들어 줍니다. 


1. 설정하기 나름이지만, 기본적으로 api/Controller이름 을 사용합니다.


2. 컨트롤러 클래스 이름을 PlayerController 로 생성했습니다. 


express 버전에서는 이렇게 까지 안만들어 졌던거 같은데....


3. 일단 이대로 빌드하고 실행해도 결과를 확일 할 수 있습니다.


실행하면 IE로 뜨게 되는데 그냥 내비두고, 크롬에서 주소창에 IE에서 보여준 주소 뒤에 /api/Player 를 입력하면 결과를 확일 할 수 있습니다.


굳이 크롬으로 하는 이유는 IE는 JSON 파일을 다운로드 받으려고 하고, 크롬은 XML 로 바로 보여줘서 입니다.


4. 이제 GET 요청시 보내줄 데이터를 위에서 생성한 Player 클래스와 연결해 봅시다.

PlayerController 클래스를 수정합니다. 


이제 확인을...


ID 로 요청해 봅니다.


POST 와 DEL 은 확인하려면 Web 페이지도 만들어야 하니 여기까지...


이정도만 만들어두면 어느 웹 or 앱 에서 Request 만해서 똭! 해서 멋진걸 만들수 있습니다.




'In to the C#' 카테고리의 다른 글

C# 6.0 New Features  (2) 2015.01.29
[C#] Task의 작업완료  (2) 2014.01.26
[C#] Action, Func 그리고 Task  (0) 2014.01.26
[C#] Attribute : 속성  (0) 2014.01.24
Lazy Initialization  (0) 2014.01.20
In to the C# 2014. 1. 26. 22:41

[C#] Task의 작업완료

Task는 기본적으로 ThreadPool을 사용하여 비동기 작업을 쉽게 구현할 수 있도록 도와 줍니다.

작업 후 동기화는 방법도 여러가지가 있습니다. 먼저 예제가 있습니다.

 

Line 1 ~ 2까지는 Action을 정의 한 후 Task로 시작 하였습니다.

Line 4는 Task의 작업을 기다리고 있습니다. Task의 작업이 오래 걸리면 중간의 작업이 완료될 때 까지 기다릴 수 있습니다.

Line 5는 조금 색다른 기능입니다. Task의 작업이 끝나면 t1의 작업이 끝나면 파라미터로 넘긴 익명메소드가 실행이 될 수 있도록 합니다. 이런 작업은 미리 후반 작업을 설정함으로써 Task 작업이 언제 종료될지 기다리고 관리할 필요가 없어집니다.

예제에는 _t1은 t1의 작업이 끝나면 파라미터 익명 메소드에 자기 자신을 파라미터로 넘깁니다.

Line 6은 Line 5와 똑같으며, 단시 리턴값이 있는 것입니다. Line 7은 return 값을 화면에 출력하는 것입니다.

 

Line 7에서 retTask.Result를 사용했는데 Result를 호출하면 retTask의 작업이 완료되기 까지 기다립니다. 중간에 Wait 메소드를 호출할 필요가 없는 거죠. 어차피 Result를 호출한다는 것은 작업이 완료되기 까지 기다리겠다는 뜻이니까요.

 

여기까지 Task의 동기화에 대해서 알아봤습니다.

 

'In to the C#' 카테고리의 다른 글

C# 6.0 New Features  (2) 2015.01.29
[C#] REST API 만들기  (0) 2014.02.13
[C#] Action, Func 그리고 Task  (0) 2014.01.26
[C#] Attribute : 속성  (0) 2014.01.24
Lazy Initialization  (0) 2014.01.20
In to the C# 2014. 1. 26. 22:40

[C#] Action, Func 그리고 Task

C#의 대리자(delegate)와 Task에 대해서 알아보겠습니다. 대리자와 Task는 항상 따라다니는 기술이며, 수많은 오픈소스도 이 두개의 개념을 적극적으로 사용하여, 비동기 로직을 구현하고 있습니다. Action과 Func은 대리자로 구현되어져 있습니다. Action이 어떻게 구현 되었는지 보겠습니다.

 

정말 별거 없습니다.

잠시만!! Action과 Func의 차이점에 대해서 얘기 하자면, Action은 파라미터만 있는 대리자 역활을 합니다. 즉, 리턴 할 수 없는 대리자 입니다. 반면에 Func은 파라미터 및 리턴 모두 할 수 있는 대리자 입니다. 이 둘의 차이점은 이것 말고는 없습니다.

 

Action은 이런 식으로 16개의 파라미터를 갖을 수 있으며, Func은 16개의 파라미터와 1개의 리턴을 갖을 수 있습니다.

Action과 Func은 굳이 정의할 필요가 없을 듯 보이지만 많은 라이브러리에서 이런 공통적인 대리자를 사용함으로써 사용자의 입장에서 보면 어려움 없이 사용할 수 있도록 하였습니다.

 

간단히 Action 정의 및 사용법에 대해 보겠습니다.

 

Line1을 보면 파라미터 없는 Action을 사용한 것입니다. action이라는 대리자에 익명함수를 정의 한 것입니다.

Line2는 string 파라미터를 갖는 Action입니다. Line3은 string과 int형 파라미터를 갖는 Action 입니다.

Line 5 ~ 7은 함수 사용하듯이 사용합니다.

 

이번엔 Func 입니다.

 

Func은 리턴 값이 꼭 있어야 합니다. 리턴 타입은 가장 마지막에 적어주면 됩니다.

 

이렇듯이 Action과 Func은 함수를 객체처럼 사용할 수 있습니다. 지금까지 Action과 Func을 본 이유는 Task를 설명하기 위해서 입니다.

Task는 C#에서 비동기 작업을 작성하기 위한 코드를 단순화 시켜줍니다. Task는 닷넷프레임워크에서 제공하는 ThreadPool에서 작동합니다. 아쉽지만 Task를 사용할 때는 순서나 시작 시점을 사용자가 지정할 수 없습니다. 즉, Task는 어떠한 비동기 작업을 수행한다라고만 지정해 줄 뿐 수행에 대한 컨트롤을 하기 어렵습니다. 그런데 Task를 왜 쓰냐면 아래와 같습니다.

 

1. 이미 만들어진 Thread에서 작동하기 때문에 Thread를 생성할 필요가 없습니다. Thread생성 비용을 줄일 수 있습니다.

2. 프로그래밍의 복잡도가 낮아집니다. 익명함수를 만들어 Task 실행만 해주면 됩니다.

3. 프로그래밍 복잡도가 낮아지므로써 병행 작업이 가능한 코딩이 쉽습니다.

4. ThreadPool의 모든 Thread가 작업중일 때는 추가 Thread가 생성이 됩니다. 이는 언덕등반오르기 알고리즘을 사용합니다.

 

마이크로소프트에서는 병행프로그래밍을 작성할 때는 가능한 Task를 사용하길 권하고 있습니다. 빠르고 안전하기 때문입니다. 그럼 언제 Thread를 사용하느냐하면,

 

1. 단독 쓰레드에서 안정적으로 지속적인 백그라운드 작업이 필요할 경우 Thread를 생성하여 작업 합니다.

 

굳이 시작시점과 종료시점을 정해야 할 작업이 아니라면 Task를 사용하는 것들 권하는 것입니다. 편하고, 빠르니까요.

 

이 Task는 생성될 때 생성자 파라미터에 Action, Action<object>, Func<TResult>, Func<object, TResult> 형식을 넘길 수 있습니다. 아래와 같이 말이죠.

 

이렇게 Task는 대리자를 통한 익명함수를 넘겨주면서 작업을 비동기 방법으로 수행 할 수 있도록 해줍니다.

Task는 굳이 생성자로 만들고 Start를 해줄 필요는 없습니다. Task는 Factory 메소드가 있어 익명함수를 만들어 넘겨주면 바로 실행 할 수 있도록 해주는 기능도 있습니다.

 

이렇게 하면 Task 생성 및 Start 메소드 호출을 할 필요는 없습니다. 여기까지 Action과 Func 그리고 이들을 사용하는 Task와 Task의 시작에 대해서 알아봤습니다. 다음 포스트에는 Task를 사용하는 방법에 대해서 간단히 알아보겠습니다.

 

'In to the C#' 카테고리의 다른 글

[C#] REST API 만들기  (0) 2014.02.13
[C#] Task의 작업완료  (2) 2014.01.26
[C#] Attribute : 속성  (0) 2014.01.24
Lazy Initialization  (0) 2014.01.20
BookSleeve - Pipelined .NET bindings for redis  (0) 2014.01.17
In to the C# 2014. 1. 24. 14:23

[C#] Attribute : 속성

Attribute는 속성이나 특성이라는 뜻을 가지고 있습니다.

C#에는 Attribute라는 기능이 있는데, 이것은 클래스나 멤버(메소드/변수)에 Attribute를 지정할 수 있습니다.

Attribute를 지정하는 클래스가 특별히 기능을 하는 것은 아니고, 제 3자가 특정 클래스의 Attribute를 확인해서 어떠한 작업을 처리 하려고 하는 것입니다.

HTML도 속성이 있는 것이 그 자체로 무언가 되는것이 아니라, 브라우저가 파싱해서 보여주고, Javascript가 id로 엘리먼트를 찾을 수 있듯이 말이죠.

 

저는 Attribute를 이용해서 메시지 분배 처리기를 간단하게 구현하였습니다. 보통은 Map이나 Dictionary에 메시지타입과 함수포인터를 연결해놓기 마련인데요. 결과론적으론 제가 구현한 방법도 같은 의미이지만 구현 과정을 보면 코딩의 양을 줄여 줄 수 있는 방법이기도 합니다. 왜냐면 메소드에 Attribute만 지정해주면 되거든요.

 

간단하게 MessageType을 정의 하였습니다.

 

그리고 Attribute 클래스를 정의 하였습니다.

MessageDistributorAttribute 클래스에서도 AttributeUsage Attribute를 사용하는 군요. 메소드에만 MessageDistributorAttribute를 붙일 수 있도록 설정한 것입니다. 그 외엔 Attribute 클래스를 상속한 것 말고는 정말 별다른 것 없는 클래스 입니다.

 

위 소스는 메소드 집합 클래스 입니다.

보통 서버 프로그래밍 할 때 메시지 처리 메소드(함수)의 특징은 파라미터들의 타입과, 리턴 타입이 동일하게끔 설계를 하지요? 이 것도 선조건으로 동일하게 구현해야겠네요. 메소드 구현전에 MessageDistributor라는 Attribute를 추가 해줍니다.

 

아! 클래스 이름은 MessageDistributorAttribute인데 사용할 대는 Attribute를 빼네요? 컴파일러가 알아서 해석합니다. 그냥 그렇게 구현한다고만 기억해두시기 바랍니다.

 

JOIN(가입) - MyMethod1 실행

LOGIN(로그인) - MyMethod2 실행

REQUEST(요청) - MyMethod3 실행

 

이런식으로 되도록 Attribute를 사용 했습니다.

다음은 Main 메소드 입니다.

 

Line 6은 MethodSet 인스턴스에서 타입 정보를 가져 옵니다.

Line 8은 MethodSet 인스턴스에서 메소드 리스트를 가져옵니다.

Line 11은 Method에서 Attribute 배열을 가져옵니다.

Line 16 ~ 18은 Attribute 배열에서 MessageDistributorAttribute 인스턴스를 골라 냅니다.

Line 27은 MethodInfo에서 실행하가능한 Method delegate를 만들어 냅니다.

Line 34 ~ 40 까지는 table에서 MessageType에 따라 메소드를 호출 하는 로직입니다.

Line 43 ~ 55 까지는 MethodSet 인스턴스에서 메소드를 delegate로 뽑아 내는 로직입니다.

 

이런식으로 하면 MessageType이 늘어나고, 처리 Method가 늘어날때마다 table.Add를 줄줄이 써줄 필요는 없게 될 것입니다.

아래는 전체 소스 입니다.

 

아래는 실행 결과 입니다.

조촐하네요;;;

 

 

간단하게 나마 Attribute에 대해서 알아봤는데요.

Attribute는 C#에서 생각보다 많이 사용하게 되니까 알아두시면 좋고, 직접 구현해서 사용하면 성숙한 프로그래머가 된듯한 느낌도 들것 입니다~

'In to the C#' 카테고리의 다른 글

[C#] Task의 작업완료  (2) 2014.01.26
[C#] Action, Func 그리고 Task  (0) 2014.01.26
Lazy Initialization  (0) 2014.01.20
BookSleeve - Pipelined .NET bindings for redis  (0) 2014.01.17
Delegates와 Serialization  (0) 2014.01.17
In to the C# 2014. 1. 20. 18:03

Lazy Initialization

Lazy initialization.

게으른 초기화.

 

네. 초기화를 뒤늣게 하는 것입니다.

가령 인스턴스를 만들 때 인스턴스 멤버들이 초기화 되는데 이 멤버들을 필요한 시점에 초기화 하는 것입니다.

 

Q. 언제 사용하기 위해 이런 개념이 생겨난걸까요?

A. 필드가 많은 경우 사용되지 않는 상황이나 초기화를 빨리하고 부가 작업을 뒤로 미루고 싶을 때 적합합니다.

 

Q. 그럼 언제 사용하지 않는게 좋을까요?

A. 최적화를 통해 성능 저하를 해결해야 하는 상황이 아니라면 초기화의 지연을 사용하지 않아야 합니다. 어떤 경우에는 이런 기법이 디버깅을 어렵게 만듭니다.

 

위 소스 예제는 C#에서의 Lazy 클래스를 설명해줍니다.

 

Line 17과 같이 객체를 생성하여도 LazyList는 값이 생성되지 않은 상태 입니다.

Line 19와 같이 값을 참조 하기만 하여도 LazyList는 생성이 됩니다. Lazy 객체의 Value는 읽기 전용이라 바로 값을 쓰지 못합니다. 소스와 같이 값을 참조한 다음에 활용하여야 합니다.

 

Line 25와 같이 Test 객체에 List가 채워진걸 보실 수 있습니다.

 

이 Lazy객체는 생각보다 빠르지 않습니다. 퍼포먼스가 떨어집니다. 그러므로 가능한 사용하지 않고, 첫번째 질문처럼 해당 사항이 발생하지 않을 때에는 사용하지 않는 편이 좋습니다.

 

C#에서 Lazy initialization의 대표적인게 LINQ에서 사용이 되는데요. LINQ에 대해 제대로 모르면 심각하게 삽질을 할 수 있는 경우가 생깁니다.

아래 소스를 먼저 보시겠습니다.

 

 

위 소스를 보시면 Line 15는 MyList의 요소를 꺼내요 factor를 곱한 뒤 selected에 넣는 그런 내용입니다.

그런데 Line 17에서 factor를 20으로 변경해주었군요.

 

Line 21에서 linq의 결과물을 프린트 하고 있습니다.

결과는 아래..

 

 

 

네. 의도한 바와 같이 10이 곱해진게 아니라 20이 곱해졌습니다.

위의 코드에선 정확히 selected.ToList()를 할 때, 결과가 정해지게 되어 있습니다.

 

이런 점을 유의 하여 코드를 작성할 수 있도록 해야 합니다.

 

끝~

'In to the C#' 카테고리의 다른 글

[C#] Action, Func 그리고 Task  (0) 2014.01.26
[C#] Attribute : 속성  (0) 2014.01.24
BookSleeve - Pipelined .NET bindings for redis  (0) 2014.01.17
Delegates와 Serialization  (0) 2014.01.17
Dispose in C#  (0) 2014.01.14
In to the C# 2014. 1. 17. 16:56

BookSleeve - Pipelined .NET bindings for redis

Redis

key-value 저장을 위한 메모리 서버로 string, hash, list, set, sorted set을 지원합니다.

Redis는 BSD 라이센스가 있는 오픈소스이며, windows용 서버는 정식 지원하지 않고 있습니다.

다만 windows에서 돌릴 수 있도록 비공식 프로젝트가 있긴 합니다.

https://github.com/MSOpenTech/redis


C#에서도 Redis client를 위한 다양한 라이브러리들이 존재합니다.

그런데 여기선 가장 쓰기 쉬운 라이브러리 BookSleeve를 소개하려 합니다.


BookSleeve의 장점은 세가지가 있습니다.


  1. 스레드로 부터 안전함.
    BookSleeve는 하나의 커넥션만으로 여러개의 스레드에서 동시 접근을 하여도 안전성을 보장 합니다.

  2. 비동기 처리
    BookSleeve는 모든 커맨드가 Task를 리턴하게 됩니다. 즉, 내부적으로 blocking되지 않고, 넘어갑니다. 이것은 여러개의 커맨드를 동시에 수행하고 한번에 동기화 할 수 있습니다.

  3. 사용이 쉽다
    네 사용이 너무 쉽습니다. 도큐먼트 찾기가 어려워서 그렇지 조금만 알면 금방 합니다.


위 소스는 byte[] 입출력 / sorted set 사용법 / publish, subscribe 사용법에 대한 내용이 포함되어져 있습니다.


Line 22 ~ 24는 string을 byte[]로 변환한 뒤 Redis에 저장 / 읽기를 보여주고 있습니다. “Park”은 key이며, “string test”는 value입니다. 참고로 Line 24에서 result.Result를 하게 되면 비동기화 작업 result가 종료될 때까지 기다린다음 Result값을 가져옵니다. 즉, 굳이 result.Wait()를 할 필요가 없다는 뜻입니다.


Line 27 ~ 28은 sorted set에 값을 넣습니다. “mySet”은 set key이며, “Kwon”은 value, 99는 score입니다. sorted set은 score의 값에 따라 정렬을 하며, 가장 작은 score는 rank가 0이 됩니다. rank를 역순으로 구할 수도 있습니다. Line 35 ~ 36이 역순으로 rank를 구한 다음 화면에 rank를 출력합니다. Rank 메소드 true면 올림차순, false이면 내림차순 입니다.


Line 39 ~ 40은 value가 없을 때 요청을 하면 어떻게 나오는지 알려줍니다. “mySet”에는 “Bong”이라는 value가 없으며, 결국 rank값은 null이 되어서 return 되었습니다.


Redis는 Publish/Subscribe가 있습니다. 여러 클라이언트간 채널을 통해 메시지를 주고 받을 수가 있습니다. Line 43 ~ Line 58이 그 예제를 보여주고 있습니다. 

Line 43은 먼저 Subscribe채널을 얻어옵니다. 채널로 메시지가 오면 처리할 대리자 Action<string, byte[]>를 정의 하구요. 채널 이름과 대리자를 등록합니다. subConn.Subscribe(“channel1”, action);

Line 54는 패턴구독을 정의 한 것입니다. “cha*”라고 채널명을 입력하였습니다. cha로 시작하는 채널명에 Publish가 되는 것이 있으면 모두 받아온다는 의미 입니다. 

Line 58은 “channel1”이라는 채널로 “Publish!!”라는 string을 publish 하였습니다. 그러면, 이미 등록된 대리자 action으로부터 메시지를 받아 올 수 있습니다.


여기서 대리자를 보면 알 수 있듯이 byte[] 배열의 데이터가 넘어옵니다. 즉, 보낼 때도 string이 아닌 byte[] 배열로 보낼 수도 있습니다.

이것을 활용하면 서버간 메시지 송수신 역할의 서버로도 활용할 수가 있습니다.


위 소스의 실행결과는 아래와 같습니다. 




'In to the C#' 카테고리의 다른 글

[C#] Attribute : 속성  (0) 2014.01.24
Lazy Initialization  (0) 2014.01.20
Delegates와 Serialization  (0) 2014.01.17
Dispose in C#  (0) 2014.01.14
Thread Pool과 Task  (0) 2014.01.14
In to the C# 2014. 1. 17. 14:32

Delegates와 Serialization

C#에서 클래스를 직렬화 하는 것은 매우 쉽습니다.

클래스 선언 전에 [Serializable] attribute만 추가 해주면 아주 멋지고 쉽게 직렬화가 가능합니다.


그런데 아래와 같은 delegates(이하 대리자)가 멤버로 있을 때 문제가 될 수 있습니다.



예제는 Action이 있는데요. C#의 대표적인 대리자가 있습니다. 대리자도 하나의 객체 이기 때문에 위와 같이 클래스를 선언하고 인스턴스를 직렬화 하게 되면 대리자도 같이 직렬화가 됩니다.


여기서 중요한건 역직렬화 할 때 잘 될 수도 있고, Exception 에러가 날 수도 있습니다. 대리자를 직렬화 하는건 매우 어려우며, 하더라도 내부의 타겟 객체의 모든 것을 제대로 직렬화 하는걸 보장 하지 않기 때문입니다.

결론적으로 대리자는 직렬화 대상에서 빼야 하며 설계 시 아래와 같이 해주어야 합니다. 개발자가 어떻게 직렬화 할 수 있는 문제가 아닙니다!!



Protobuf-net에서는 Protomember로 선언을 하여도 직렬화 대상이 되질 않으며, 역직렬화 시 null 값이 채워집니다.

'In to the C#' 카테고리의 다른 글

Lazy Initialization  (0) 2014.01.20
BookSleeve - Pipelined .NET bindings for redis  (0) 2014.01.17
Dispose in C#  (0) 2014.01.14
Thread Pool과 Task  (0) 2014.01.14
Google Protocol Buffer 사용해보기 with C# - 세번째  (0) 2014.01.14
In to the C# 2014. 1. 14. 16:17

Dispose in C#

C#은 Garbage Collector에 의해 Heap메모리를 관리하고 있습니다.

C나 C++ 처럼 메모리 해제를 프로그래머가 대신 해줄 수는 없습니다. 구글에 C#에서 인스턴스 해제를 어떻게 하냐는 질문에는 Dispose를 구현하라 라고 나와있는 글들이 많습니다.


그러나 Dispose패턴은 메모리 해제와 관련이 없습니다. Dispose의 정보가 상당히 와전된 경우 입니다. 저도 그렇게 알고 있었는데 다시 찾아보니 그게 아니더군요.


Dispose의 사전전 의미로는 처분하다/처리하다/폐기하다 라는 뜻으로 왠지 메모리 해제와도 관련이 있어 보이긴 합니다.


이제 자세히 알아보겠습니다.

Dispose의 구현은 대게 Close나 Dispose 멤버 메소드를 갖고 있는 인스턴스를 멤버로 갖고 있을 때 구현해주어야 합니다. 즉, 런타임중 관리되지 않는 핸들, 데이터베이스 연결등 리소스에 대한 제어를 명시적으로 해주기 위함입니다. (Finalize 구현으로 암시적으로 수행할 수도 있습니다.)


명시적으로 리소스를 정리하기 위해서는 Dispose를 구현하여야 합니다. 예제를 보겠습니다.


DisposeableObject는 Stream을 멤버로 보유하고 있습니다. 우리는 DisposeableObject를 다 쓴 경우 Stream의 핸들을 해제 시켜주어야 합니다. 그래서 명시적으로 DisposeableObject는 Dispose를 구현하도록 만들어 준 것입니다. 


그리고 Line 43에 보면 GC.SuppressFinalize를 호출합니다. 이것은 Finalize가 호출 되지 않도록 막은 것입니다. MSDN에서는 GC.SuppressFinalize를 호출하기를 권장합니다. 단, Finalize에서 호출되어야 하는 패턴이 존재한다면 이 규칙은 적용되지 않습니다. 

DisposeableObject를 다 사용한 경우 Dispose 메소드를 호출하는 것이 가장 이상적입니다.


위 내용을 요약하면 멤버 객체중에 Close, Dispose메소드를 갖고 있는 경우 Dispose를 구현하라는 내용입니다. 


추가  : Close와 Dispose의 개념적 차이

한줄로 표현한다면 Close된 객체는 재사용이 가능하고, Dispose된 객체는 재사용이 불가능 하다고 생각하면 됩니다. 단 Close에서 Dispose를 호출한다면 이또한 사용할 수 없겠죠.


추가2 : 그럼 메모리 해제는?

Garbage Collector가 알아서 잘할테니 건드리지 마세요.


In to the C# 2014. 1. 14. 14:09

Thread Pool과 Task

C#에서 Task는 비동기 작업을 나타낸다고 합니다.

Task는 .NET Framework4.0 부터 사용 할 수 있습니다. Task의 작동방식은 .NET Framework에서 관리되고 있는 ThreadPool에서 작동합니다. 그럼 ThreadPooling에 대해서 간단히 알아 보겠습니다.

닷넷 프레임워크로 작성된 어플리케이션은 실행되면서 자동으로 ThreadPool도 생성이 됩니다. ThreadPool을 사용하는 Task와 비동기 타이머 같은 것들을 지원하기 위해서죠. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace ConsoleApplication26
{
  class Program
  {
    static void Main(string[] args)
    {
      int minWorkerThreads = 0;
            int minCompletionPortThreads = 0;
            ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads); 
 
            int maxWorkerThreads = 0;
            int maxCompletionPortThreads = 0;
            ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads); 
 
            Console.WriteLine("Min Worker Threads : {0}, max Worker Threads : {1}", minWorkerThreads, maxWorkerThreads);
            Console.WriteLine("Min CompletionPort Threads : {0}, max CompletionPort Threads : {1}", minCompletionPortThreads, maxCompletionPortThreads);
        }
    }
}


위 코드는 ThreadPool의 최소, 최대 Thread 개수를 구합니다. 실행하면 아래와 같은 결과가 나옵니다.



ThreadPool은 최소 4개부터 최대 1023개까지 작업자 스레드를 갖을 수 있습니다.

CompletionPort Thread는 비동기 I/O스레드의 수 입니다. 비동기 I/O스레드의 수는 4 ~ 1000개 까지 갖을 수 있습니다.


ThreadPool은 언덕등반 알고리즘(Hill Climb Algorithm)에 의해 자동으로 스레드가 생성이 됩니다.

즉, Task를 쓸 경우 Thread의 개수는 닷넷프레임워크가 알아서 조절해 준다는 것이지요.

간단한 Task를 만들어서 ThreadPool이 어떻게 작동하는지 보겠습니다.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication26
{
    class Program
    {
        static List<int> threadIdList = new List<int>();
        static object lockList = new object();

        static void AddBag(int threadId)
        {
            lock (lockList)
            {
                if (!threadIdList.Contains(threadId))
                    threadIdList.Add(threadId);
            }
        }

        static void Print()
        {
            Console.WriteLine("Thread Count : {0}, Thread IDs : {1}", threadIdList.Count, string.Join(",", threadIdList));
        }

        static void Main(string[] args)
        {
            List<Task> taskList = new List<Task>();
            for (int i = 0; i < 100; i++)
            {
                Task t = new Task(() =>
                {
                    AddBag(Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(1000);
                });

                taskList.Add(t);
                t.Start();

            }
            taskList.ForEach(x => x.Wait());
            Print();
        }
    }
}


Main 메소드에서 스레드에 sleep을 1초 주었습니다.

처음엔 4개의 thread에서 Task의 작업을 수행하게 됩니다. 그런데 sleep(=부하)가 일어나다 보니 닷넷 프레임워크에선 수행속도가 늦어짐을 감지하고 ThreadPool에서 Thread를 더 추가해 줍니다. 위의 결과는 아래와 같습니다. 최종적인 결과로는 사용한 Thread의 개수는 9개 입니다.



아마도 Thread.Sleep 메소드가 소스에서 제거가 된다면 Thread는 4개만 돌아갈 것입니다. Task를 수행하는데 delay가 없기 때문이지요.


여기서 전 좀 더 많은 스레드를 생성해도 성능상 문제가 없을 텐데라는 생각이 들면서, 좀 더 쉽고, 좀 더 간단하게(직접 ThreadPool을 구현하지 않아도 되는!)Thread의 수를 늘릴 방법을 찾았습니다.


처음에 어느 정도의 스레드의 수를 할당해 놓으면 Task가 delay되는 것을 감지하고 thread를 할당해주는 작업이 생기지 않을 것이기 때문입니다.


그럼 최소 스레드의 개수를 늘려 보겠습니다.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication26
{
    class Program
    {
        static List<int> threadIdList = new List<int>();
        static object lockList = new object();

        static void AddBag(int threadId)
        {
            lock (lockList)
            {
                if (!threadIdList.Contains(threadId))
                    threadIdList.Add(threadId);
            }
        }

        static void Print()
        {
            Console.WriteLine("Thread Count : {0}, Thread IDs : {1}", threadIdList.Count, string.Join(",", threadIdList));
        }

        static void Main(string[] args)
        {
            ThreadPool.SetMinThreads(50, 100);
            List<Task> taskList = new List<Task>();
            for (int i = 0; i < 100; i++)
            {
                Task t = new Task(() =>
                {
                    AddBag(Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(1000);
                });

                taskList.Add(t);
                t.Start();

            }
            taskList.ForEach(x => x.Wait());
            Print();
        }
    }
}


메인 메소드에 ThreadPool.SetMinThreads를 이용하여 최소 워커 스레드를 50개로 설정하였습니다.

결과는 아래와 같습니다.



Thread Count가 50개가 되었습니다.

ThreadPool 설정이 Task가 작동하는데 영향이 있다는 것을 확인하였습니다.

ThreadPool 의 개수는 적절히 조절을 해야 합니다. 너무 많은 수는 어플리케이션의 성능을 떨어뜨리니 주의 하는 것이 좋습니다.


아! 마지막으로 중요한 것!

Task 수행은 위에도 설명하였듯이 ThreadPool에서 해당 Task가 작업을 하게 됩니다. 그런데 만약 여기에서 Sleep을 걸게 되면 안됩니다. 왜냐면 전자 Task가 수행되는 Thread는 후자 Task가 대기 할 수도 있기 때문입니다. 후자 Task는 전자 Task가 Sleep을 하고 있기 때문에 기다리다가 다른 Thread로 갈아탑니다. 이렇게 되면 보이지도 않게 퍼포먼스가 떨어지게 됩니다.


즉, 이말은 Task를 수행한다는 것은 최악의 상황일 때 즉시 실행된다는 것을 보장하지 못한다는 것입니다. 정말 성능적으로 중요한 백그라운드 작업이라면 Task를 사용하지 말고 Thread를 생성해서 사용하여야 합니다.


끝~