336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

VisualStudio 2012, .NET Framework 4.5부터 지원하는 대표적인 기능 중 하나가 바로 비동기 프로그래밍 입니다. 여기서는 비동기 프로그래밍이 무엇인지 정리 드리고자 합니다..

비동기가 왜 필요할까?

웹 2.0 시대가 오면서 OpenAPI, 매쉬업을 통한 개발이 각광받게 되었습니다. 즉, 다른 웹사이트의 자원을 요청하고 가공하여 서비스를 제공하는 경우가 많아지게 된 것이죠. 하지만 이러한 외부 서비스 자원은 가장 느리고 불안정한 자원으로써 서비스의 대기시간이 길어지게 하는 원인으로 작용하게 됩니다. 이렇게 늘어난 서비스 대기시간은 필연적으로 병목을 발생시키고 이는 전체적인 Application의 반응성을 떨어뜨리게 됩니다. 급기야 사용자가 늘어날 수록 비용은 급상승하게 되고 경우에 따라서는 서비스 자체를 불가능하게 만들기도 합니다.

이러한 문제는 비동기 프로그래밍을 통해서 해소될 수 있는데, 비동기 프로그래밍을 위해서는 전문적인 지식과 복잡한 설계를 필요로 합니다. 따라서 고객의 만족을 위해 신뢰할 수 있는 소프트웨어 개발에 집중해야 할 개발자는 비동기 처리를 위해 복잡한 로직을 생산하고 많은 시간을 쓰게 됩니다.

따라서 .NET Framework 4.5부터는 이러한 문제를 해소하고자 async와 await를 통한 비동기 프로그래밍을 지원하고 있습니다. async와 await를 바탕으로 기존의 동기방식의 심플한 구조로 작성할 수 있게 지원하며, 기존에 반복적으로 작성하곤 했던 복잡한 기능들은 .NET Framework가 대신해주게 됩니다.

Application 구분비동기 지원
Web accessHttpClient , SyndicationClient
파일 입출력StorageFileStreamWriterStreamReaderXmlReader
이미지 처리MediaCaptureBitmapEncoderBitmapDecoder
WCF 프로그래밍동기, 비동기 처리를 위한 지원

Async와 Await

async와 await는 비동기로 수행될 메서드를 작성하는데 필요한 핵심적인 키워드 입니다. 이를 통해 비동기로 수행될 메서드를 기존의 동기 메서드 처럼 쉽게 직관적으로 작성할 수 있습니다. async, await를 사용한 메서드를 비동기 메서드(Async Method)라고 합니다. 아래는 비동기 메서드에 대한 간단한 예제입니다.

01// 주목해야할 세가지
02//  - 비동기를 수행하는 메서드는 반드시 aync keyword로 수식해야합니다.
03//  - async keyword가 수식된 메서드는 Async Method로써 반환형은 반드시
04//    void, Task, Task<T> 중 하나여야 합니다. 여기에서는 Task<int>를
05//    반환형으로 지정하였는데 이는 비동기 처리 작업 후 반환되는 데이터가 int이기 때문입니다.
06//  - Convention으로써 Method명을 Async로 끝마침으로써 이 메서드가 비동기 메서드라는 것을 명시해주세요.
07async Task<int> AccessTheWebAsync()
08{
09    HttpClient client = new HttpClient();
10 
11    //아래는 HttpClient에 추가된 GetStringAsync라는 비동기 메서드를 통해서
12    //다른 웹사이트의 접근하는 예제입니다. 이 메서드는 Task<T>를 반환하는데
13    //여기서 Task<T>은 비동기 작업을 나타내는 Type으로써 비동기로 진행 중인 작업정보를 제공합니다.
14    //즉, 아래 Task<string>은 string을 반환하는 비동기 작업을 나타냅니다.
15    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
16 
17    //비동기 메서드 내에도 기존의 동기방식의 처리코드를 포함할 수 있습니다.
18    DoIndependentWork();
19 
20    // Task<string> Type으로 지정된 getStringTask 변수 앞에
21    // await를 지정하였습니다. 이는 변수 getStringTask가 가리키는
22    // 비동기 작업이 완료될 때까지AccessTheWebAsync 메서드를 중단시킵니다.
23    //  - 비동기 작업 getStringTask가 완료될 때 까지 기다립니다.
24    //  - 기다리는 동안 현재 쓰레드의 제어는 AccessTheWebAsync를 호출한 부모에게 넘어갑니다.
25    //  - getStringTask이 완료되기 전까지 작업이 재개되지 않습니다.
26    //  - getStringTask의 작업이 완료되면 await 명령은 변수 urlContents에 작업결과를 담고
27    //    중단된 시점부터 다시 작업을 재개시킵니다.
28    string urlContents = await getStringTask;
29 
30    // 이 비동기 메서드가 완료되길 기다렸던 이 메서드의 호출자에게
31    // 결과를 반환합니다.
32    return urlContents.Length;
33}

위 코드를 바탕으로 async method의 핵심을 요약하면 다음과 같습니다.

  • asnyc로 수식된 메서드는 비동기로 수행되는 메서드가 됩니다.
  • Convention에 의해 메서드명의 접미사는 Async가 됩니다.
  • 비동기 메서드는 다음과 같은 반환형 중 하나를 가집니다.
    • Task<T>: 만약 비동기 메서드의 수행 후 반환되는 데이터가 있다면 반환형을 T에 명시해야 합니다.
    • Task: 비동기 메서드 수행 후 반환되는 데이터가 없을 때 사용합니다.
    • void: 비동기 메서드가 이벤트 핸들러로 사용될 때 void로 명시합니다.
  • 비동기 메서드는 적어도 1개 이상의 await를 포함할 수 있습니다. 하지만 반드시 포함해야 하는 것은 아닙니다. await를 만나면 해당 지점에서 작업이 중단되고 제어는 비동기 메서드를 호출한 호출자에게 넘어가게 됩니다. 이와 별개로 비동기 작업은 따로 진행되게 됩니다. 비동기 작업을 끝마치면 중단된 지점으로부터 다시 작업을 재개하게 됩니다.

위의 예제와 같이 작성된 비동기 메서드는 다음과 같은 방법으로 사용될 수 있습니다.

1//Event Handler에서 다음과 같이 처리하실 수 있습니다.
2private async void Button_Click(object obj, EventArgs e)
3{
4    Task<int> accessTheWebTask = AccessTheWebAsync();
5    string result = await accessTheWebTask;
6    tbxAverageAge.Text = result;
7}

예외처리와 반복문 처리

전통적인 비동기 처리방식으로는 예외처리와 루프처리는 매우 까다로운 작업 중 하나였습니다. 하지만 .NET Framework에서는 동기방식으로 처리하는 것과 동일한 방식으로 작성할 수 있으며 나머지 어렵고 복잡한 부분은 컴파일러가 알아서 처리합니다.

01//비동기 방식으로 처리하기 매우 복잡했던 반복제어, 예외처리도
02//.NET Framework에서는 기존 처리방법과 동일하게 작성하시면 됩니다.
03async Task<int> GetAverageAgeAstnc(string[] userNameList)
04{
05   int averageAge = 0;
06   HttpClient client = new HttpClient();
07   string url = "http://example.com/GetAge?userName=";
08   try
09   {
10      foreach(string userName in userNameList)
11      {
12         int age = await client.GetStringAsync(url + userName);
13         averageAge += age;
14      }
15      return averageAge / userNameList.Length;
16   }
17   catch(Exception ex)
18   {
19      return null;
20   }
21}

실행 흐름

비동기 프로그래밍을 하기 위해 가장 중요한 것은 실행 흐름을 정확하게 이해하는 것입니다. 아래는 Windows Application에서 비동기 작업의 흐름에 대한 예제입니다.

  1. 이벤트 StartButton_Click에 의해 비동기 메서드 AccessTheWebAsync()가 호출됩니다.
  2. HttpClient 인스턴스를 생성하고 비동기 메서드 GetStringAsync()를 호출합니다.
  3. GetStringAsync() 메서드 내부적으로 주어진 주소로 요청을 보내고 응답이 올 때까지 기다리게 되며 기다리는 동안 실행 제어를 호출자에게 넘겨주면서, 현재 작업상태를 Task<string> Type으로 반환하게 됩니다. getStrignTask 변수는 진행 중인 비동기 작업에 대한 정보를 나타냅니다.
  4. 비동기 메서드 GetStringAsync() 이후의 작업인 DoIndepentWork()가 실행됩니다.
  5. DoIndependentWork()는 일반 메서드이므로 동기적으로 실행됩니다.
  6. await는 비동기 작업 getStringTask가 완료될 때까지 AccessTheWebAsync()의 실행을 중단시킵니다. 그리고 현재 쓰레드의 제어를 호출자에게 넘겨줍니다. 또한 현재 AccessTheWebAsync의 작업상태를 나타내는 Task<int>를 생성하게 되며 이를 호출자에게 전달해 줍니다. 
    주의: 비동기 작업 getStringTask가 이미 완료되었다면, 여기서 AccessTheWebAsync는 제어를 중단하지 않고 작업을 계속 이어가게 됩니다.
    제어를 넘겨받은 AccessTheWebAsync의 호출자는 비동기 작업이 완료될 때까지 다른 작업을 수행하게 됩니다.
  7. 비동기 작업 getStringTask의 작업이 완료되면 await는 getStringTask로부터 결과값을 가져와 변수 urlContents에 담고, 중단되었던 지점부터 작업을 재개 시킵니다.
  8. 최종 결과가 호출자에게 반환되게 됩니다.

위와 같은 작업 흐름을 명확하게 이해하는 것이 중요하며, 실제로 작성해보시길 권장 드립니다. 이와 관련한 상세한 예제코드와 설명은 다음 링크에서 확인하실 수 있습니다. (http://msdn.microsoft.com/ko-kr/library/hh873191.aspx)

API Async Methods

.NET Framework에서는 대부분의 시나리오에서 빈번하게 발생하는 요구사항을 해결할 수 있도록 비동기 프로그래밍을 위한 다양한 API를 제공하고 있으며 이를 통해서 비동기 처리가 가능합니다.

위 예제에서는 HttpClient의 GetStringAsync가 하나의 예로써 사용되었습니다. 이와 같은 비동기 API들은 Async라는 접미사를 가지고 있으며 반환형이 Task, Task<T>, void 중 하나이며 (awaitable)이라는 표시가 나타납니다. 

따라서 비동기 처리가 필요한 경우 해당 Async 메서드가 있는지 확인하는 것이 필요합니다. 만약 제공하고 있다면 이를 async, await로 수식함으로써 간단하게 비동기 처리로 전환하실 수 있습니다. 예를 들어 파일을 읽을 경우 기존의 동기작업은 다음과 같이 작성될 수 있습니다.

01public void ReadButton_Click(object sender, EventArgs e)
02{
03    string filePath = @"c:\tmp.txt";
04    using (FileStream sourceStream = new FileStream(filePath,
05        FileMode.Open, FileAccess.Read, FileShare.Read,
06        bufferSize: 4096, useAsync: true))
07    {
08        StringBuilder sb = new StringBuilder();
09 
10        byte[] buffer = new byte[0x1000];
11        int numRead;
12        while ((numRead = sourceStream.Read(buffer, 0, buffer.Length)) != 0)
13        {
14            string text = Encoding.Unicode.GetString(buffer, 0, numRead);
15            sb.Append(text);
16        }
17 
18        tbxResult.Text = sb.ToString();
19    }
20}

이를 비동기로 전환하고자 할 경우 해당 ReadAsync 메서드와 async와 await를 통해 손쉽게 비동기로 바꿀 수 있습니다.

01public async void ReadButton_Click(object sender, EventArgs e)
02{
03    string filePath = @"c:\tmp.txt";
04    using (FileStream sourceStream = new FileStream(filePath,
05        FileMode.Open, FileAccess.Read, FileShare.Read,
06        bufferSize: 4096, useAsync: true))
07    {
08        StringBuilder sb = new StringBuilder();
09 
10        byte[] buffer = new byte[0x1000];
11        int numRead;
12        while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
13        {
14            string text = Encoding.Unicode.GetString(buffer, 0, numRead);
15            sb.Append(text);
16        }
17 
18        tbxResult.Text = sb.ToString();
19    }
20}

이와 관련한 자세한 정보는 http://msdn.microsoft.com/library/windows/apps/Hh452713%28v=win.10%29.aspx 에서 확인 하실 수 있습니다.

Threads

비동기 메서드에서 await는 작업을 중단하며 현재 쓰레드의 제어를 부모 메서드로 넘기게 됩니다. async, await 키워드는 추가적인 쓰레드를 생성하지 않으며 따라서 비동기 메서드는 자신만의 쓰레드를 가지지 않으므로 멀티쓰레딩에 대한 고려가 불필요합니다. 비동기 메서드는 Synchronization Context에서 동작하며 그 컨텍스트의 자원을 사용합니다.

Task.Run을 통해서 작업을 백그라운드 쓰레드로 옮길 수 있지만, 백그라운 쓰레드는 해당 작업을 즉시 지원하지 않으며 사용가능해질 때 까지 기다립니다.

비동기 프로그래밍을 위해 async, await를 이용하는 것은 BackgroundWorker(현재 처리를 다른 분리된 쓰레드로 실행하는 Class)를 통한 접근보다 훨신 간결하고, 경쟁상태 등 복잡한 쓰레드에 대한 고려를 요구하지 않습니다.

async와 await

  • await는 async 메서드에서만 사용할 수 있습니다. 일반 메서드에서는 await는 단순한 식별자로 사용되게 됩니다.
  • await는 비동기 메서드 내에서 중단점을 지정하게 됩니다. await 키워드는 컴파일러에게 비동기 작업이 끝나기 전에는 현재 지점 이후로 진행할 수 없음을 알리고, 제어를 호출자에게 넘깁니다. await 키워드는 현재 실행을 잠시 보류하는 것이기 때문에, 제어가 호출자에게 넘어갈 때 finally 블럭이 실행되는 등의 경우는 발생하지 않습니다.
  • async 메서드에 await가 반드시 포함되어야 하는 것은 아닙니다. await가 없는 async 메서드는 동기 메서드와 동일하게 처리됩니다. 또한 컴파일러는 이에 대해 Waring을 생성합니다.

async, await에 대한 보다 자세한 정보는 다음 링크를 확인하세요.

반환형과 매개변수

async 메서드는 Task, Task<T>, void를 반환형으로 가질 수 있습니다. Task, Task<T>에는 async 메서드 뿐만 아니라 다른 방법으로도 생성될 수 있으며 모든 생성된 Task, Task<T>는 await가 적용될 수 있습니다.

반환된 Task는 진행 중인 작업을 나타냅니다. Task는 비동기로 진행 중인 작업상태정보, 최종 결과 정보, 실패했을 때 예외정보까지 포함하고 있습니다.

void 반환형은 보통 비동기 메서드가 이벤트 핸들로러 사용될 때 지정됩니다. 왜냐하면 이벤트 핸들러로 사용되는 delegate가 return type을 void로 정의하고 있기 때문입니다. 하지만 Task를 반환하지 않기 때문에 해당 비동기 메서드를 호출한 호출자는 비동기 메서드에서 발생하는 작업경과나 Exception에 대해서 알 수 가 없습니다.

비동기 메서드에는 out, ref와 같은 Call by reference 형태의 매개변수를 지정할 수 없습니다.

Naming Convention

비동기 메서드는 Async를 Convention에 의해 접미사로 가집니다. 하지만 event, base class, interface에는 이러한 Convention이 생략될 수 있습니다. 예를 들어 Button_Click Event를 Button_ClickAsync로 명시할 필요는 없습니다.

Refernece


출처 : http://nsinc.tistory.com/109

https://msdn.microsoft.com/en-us/library/hh191443.aspx

'Programming > C#' 카테고리의 다른 글

다른 컴퓨터 정보 가져오기  (0) 2015.12.21
ref 와 out 차이  (0) 2015.12.02
Aggregate() vs string.Join()  (0) 2015.04.09
C# Using PostMessage  (0) 2015.03.29
Random Class  (0) 2015.02.08
Posted by 역시인생한방
,

Random Class

Programming/C# 2015. 2. 8. 22:36
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Random 클래스는 난수를 얻고자 할 때 쓰는 클래스다.


생성자는 2가지가 있는데


public Random(); // 시간에 따라 달라지는 시드 값을 사용

public Random(int seed); // seed 인수에 지정된 시드 값을 사용


* Seed 의사(pseudo) 난수 시퀀스의 시작 값을 계산하는 데 사용되는 숫자


이 Seed 값은 보통 아래와 같은 unique 한 값들을 넘긴다.


Guid.NewGuid().GetHashCode() or DateTime.Now.GetHashCode()


인터페이스로는


public virtual int Next(); // 음수가 아닌 난수를 반환

public virtual int Next(int maxValue); // maxValue 보다 작은 음수가 아닌 난수를 반환

public virtual int Next(int minValue, int maxValue); // minValue(포함)보다 크고 maxValue 보다 작은 난수 반환

public virtual void NextBytes(byte[] buffer); // 지정된 바이트 배열의 요소를 난수로 채움

public virtual double NextDouble(); // 0.0 과 0.1 사이의 난수를 반환

Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Text="{StringFormat={}{0\,11:'#'#\,#}}"


{0\,11} 

자릿수가 총 11자리가 된다는 거


'#'

샾 문자를 삽입


#\,# 

3자리 수 마다 콤마 찍힘


출처 : http://msdn.microsoft.com/en-us/library/0c899ak8.aspx

Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

우선 Synchronization, 동기화의 의미에 대해 좀 살펴봐야 겠는데,

일단 동기화 라고 하는 것은 멀티스레드와 관련이 있는 단어다. 즉 싱글스레드에서는 동기화라고 하는 개념은 없다고 생각하자.

 

그럼 멀티스레드에서 동기화가 의미하는 바는, 어떤 코드의 구역이 여러 스레드에 의해 동시에 실행되지 않도록 하는 것이라 할 수 있다.

 

특정 변수에 동시에 접근하지 않도록 하는 것이 동기화 아니냐고 반문할 수도 있겠지만, 결국 특정 변수에 접근한다는 말 자체가 어떤 코드의 실행을 의미하기 때문에, 좀더 일반적인 설명은 특정 코드의 구역에 대한 동시 실행을 막는다는 것이 좋을 듯 하다.

 

위의 동기화 구현은 보통 Lock이라는 요소를 등장시켜 구현한다. 닷넷의 Monitor나 자바의 synchronized 키워드 같은 것들이 다 Lock 개념을 이용한 동기화 구현체다.

 

누구나 알고 있는 너무 당연한 이야기를 장황하게 언급한 이유는,

SynchronizationContext 가 추구하는 동기화와 차이가 있기 때문이다.

 

위에서 이야기한 동기화에서는, Lock으로 묶이는 코드의 구역이 어떤 Thread에 종속되는 것은 아니다.

즉 여러 Thread가 동시에 실행되지 못하게 할 뿐이지, 특정 Thread만 그 코드를 실행할 수 있도록 하는 것은 아니라는 말이다.

 

그런데 만약, 이렇게 동시에 실행되면 안되는 코드의 구역이 여러 곳에 분산되어 아주 많아지는 경우라면 어떤가?

일일이 코드가 동시에 실행되면 안되는지 분석하고, 그 곳을 항상 Lock으로 묶어야 한다. 코드가 점점 커짐에 따라 상황은 점점 복잡해지고 악화되고 버그도 함께 증가할 것이다. 특히 멀티스레드의 버그는 잡기 어렵기로 유명하다.

뭔가 다른 대책이 필요하다.

 

그럼 이렇게 생각해 보자. 차라리 어떤 코드 혹은 객체를 특정 Thread와 묶어 버리는 것이다. 즉 특정 코드 혹은 객체는 항상 특정 Thread만 실행할 수 있도록 하자는 것이다. 이렇게 정하고 나면, 여러 Thread가 동시에 코드 혹은 객체를 접근하지 않는다는 보장이 생기기 때문에, 멀티스레드에 대한 고려를 생각하지 않아도 된다.

코드 혹은 객체를 프로그래밍할 때도, 코드가 실행되는 시작부분(보통 함수나 메소드의 시작부분)에서만 자신을 실행하려고 하는 Thread가 자신이 소속된 Thread인지 검사하는 코드를 넣어주면 되기 때문에, 복잡한 코드의 분석이나 Lock으로 묶어야 하는 부분을 찾지 않아도 된다. 따라서 버그도 줄어들게 된다.

이것은 COM에서 STA(Single-Threaded Apartment)와 같은 개념이다.

 

이렇게 하면 결국 한가지 문제만 해결하면 된다. 다른 Thread에서 그 코드 혹은 객체에 접근하는 방법말이다.

예상하겠지만, 이를 위한 도구가 바로 SynchronizationContext 다.

 

어떤 Thread에 종속된 객체를 접근하고 싶으면, 그 객체가 소속된 Thread의 SynchronizationContext 를 얻어 와서 SynchronizationContext.Post 혹은 SynchronizationContext.Send 를 사용하면 된다.

그러면 Post 혹은 Send 에 넣어준 delegate를 해당 Thread에서 실행되도록 해 준다.

 

일반적으로 이렇게 객체와 Thread를 커플링 시키는 경우는 대부분이 GUI 관련 객체들이다. GUI 관련 객체들은 그 객체를 생성한 Thread와 종속되고, 그 GUI 관련 객체의 조작은 항상 그 객체를 생성한 Thread를 통하도록 되어 있다.

 

닷넷에는 2개의 GUI 관련 프레엠워크가 있는데, WinForm 과 WPF 다.

그리고 이 WinForm 과 WPF 를 위한 SynchronizationContext 가 있는데,

 

System.Windows.Forms.WindowsFormsSynchronizationContext 와

System.Windows.Threading.DispatcherSynchronizationContext 다.

 

둘다 System.Threading.SynchronizationContext 를 상속 받았다.

 

다음 글에서 좀더 이 3개의 클래스에 대해 좀더 자세히 살펴보자.


출처 : http://blog.naver.com/jjoommnn/130034645700

Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

앞선 글에 이어 SynchronizationContext 에 관해서 좀더 이야기 해 보자.

 

이제까지 코드 혹은 객체와 Thread를 커플링 시키는 방식으로 동기화를 구현하는 것에 관해서 이야기 했는데, 여기서 한가지 필수적인 메커니즘을 빼먹은 것 같다.

 

Thread에 종속된 객체는 그 Thread에서만 접근할 수 있고, 따라서 그 객체에 접근하기 위해서는 SynchronizationContext 를 사용해야 한다고 했다. 따라서 SynchronizationContext 는 그 Thread에게, 이 delegate를 실행시켜 달라고 요청하는 도구라고 볼 수 있다.

그렇다면 결국 그 객체가 종속된 Thread는, 요청을 받아 들이고 처리하는 메커니즘을 구현하고 있어야 한다.

 

여러가지 방법이 있을 수 있겠지만 윈도우 운영체제에서는 대부분 메세지 기반(Message Driven), 메세지 펌프로 구현된다.

앞선 글에서도 말했지만 객체와 Thread의 종속은 대부분 GUI와 관련된 경우가 많고, 윈도우 운영체제에서는 GUI와 관련해서 거의 항상 메세지 펌프가 작동하기 때문에, 이를 이용한 SynchronizationContext 는 궁합이 잘 맞는다고 할 수 있다.


이를 위해 WinForm도 그렇고 WPF에서도 실행 요청을 위한 메세지를 하나 정의(RegisterWindowMessage)해서 사용하고 있다.

 

다시 본론으로 돌아와서,

닷넷에서 볼 수 있는 SynchronizationContext 는 3가지가 있다.

 

System.Threading.SynchronizationContext

     |

     +- System.Windows.Forms.WindowsFormsSynchronizationContext

     |

     +- System.Windows.Threading.DispatcherSynchronizationContext

 

일단 SynchronizationContext 는 다른 2개의 부모 클래스인데,

MSDN의 설명으로나, Reflector 를 이용해서 소스를 보더라도, SynchronizationContext 클래스는 실제로 동기화를 수행하지 않는다.

이 SynchronizationContext 의 Post를 호출하면 그냥 ThreadPool을 이용하여 delegate를 실행해 버린다. 당연히 위에서 설명한 동기화 처리가 될리가 없다.(더군다나 SynchronizationContext는 Abstract 클래스도 아니라서 그냥 생성해서 사용할 수도 있다.)

 

이쯤되면 SynchronizationContext의 존재이유에 대해서 의문이 생기기도 하는데,

닷넷에서 모든 Thread는 자신을 위한 SynchronizationContext를 가지고 있다.

하지만 딱히 동기화를 해야 하는 객체를 소유하지 않는 일반 Thread인 경우에는 동기화를 구현할 필요가 없다. 이런 Thread는 SynchronizationContext를 가지게 된다. 뭐 디폴트 SynchronizationContext라고 생각할 수 있다.

'딱히 동기화를 할 필요없는 경우에 사용하는 동기화 도구' 라고 할 수 있는 좀 아이러니 한 상황을 위해서 말이다.

 

앞서, 특별하게 동기화를 통한 보호를 받아야 하는 경우는 GUI 관련 객체들이고, 닷넷에는 2개의 GUI 프레임워크가 있는데, WinForm 과 WPF 라고 이야기 했다.

각각을 위한 SynchronizationContext 가 WindowsFormsSynchronizationContext 와 DispatcherSynchronizationContext 다.

 

위에서 이야기 한 것 중에, 모든 Thread는 SynchronizationContext를 가진다고 했고, 디폴트로는 동기화 기능이 없는 SynchronizationContext가 사용된다고 했다. 그럼 WindowsFormsSynchronizationContext 나 DispatcherSynchronizationContext 는 언제, 어떻게 사용되는 것일까?

 

Thread를 생성할 때, 이 Thread는 WinForm GUI를 위한 Thread, 이 Thread는 WPF GUI를 위한 Thread라고 따로 생성하는 건 아니다.

또한 일반적인 용도로 생성된 Thread가 GUI 용 Thread로 바뀌는 경우도 있을 수 있다.

힌트는 서두에 밝힌 실행요청 메커니즘에 있다. 객체를 소유한 Thread가 실행 요청을 받기 위해서는 메세지 루프를 가져야 한다는 사실 말이다.

 

WinForm도 그렇고 WPF 에서도 그렇고,

WinForm용 메세지 루프로 진입하는 부분, 혹은 WPF 용 메세지 루프(Dispatcher.PushFrame)로 진입하는 부분에서, 현재 루프를 실행하는 CurrentThread에 대해, WindowsFormsSynchronizationContext 혹은 DispatcherSynchronizationContext 를 세팅하도록 되어 있다.

메세지 루프로 진입한다는 이야기는 이제부터 실행요청을 받아 들일 수 있는 상태가 되기 때문이다.

그렇기 때문에 메세지 루프를 빠져나올 때는 CurrentThread에 대해 이전 SynchronizationContext를 복구하도록 되어 있다. 메세지 루프를 빠져 나온다는 것은 더이상 실행요청을 받아들이지 못한다는 의미가 되기 때문이다.

 

그리고 DispatcherSynchronizationContext 는 Dispatcher.Invoke를 이용하여 구현되어 있다.

WPF에는 이미 객체와 Thread의 종속관계를 명확히 하면서, 다른 Thread에서 객체를 접근하는 방법으로 이미 Dispatcher의 Invoke를 마련해 놓았다. 이건 SynchronizationContext가 추구하는 동기화와 정확하게 일치한다.

따라서 DispatcherSynchronizationContext 가 Dispatcher.Invoke를 이용하여 구현되는건 필연적이다.


출처 : http://blog.naver.com/jjoommnn/130034675385

Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

닷넷 프레임워크 기반 프로그래밍에서 회자되는 내용 중에 "어려운 범주"에 속하는 주제들이 몇 가지 있는데, 그 중 하나가 AppDomain에 관련된 것입니다. 닷넷 프레임워크는 전통적인 프로그래밍 모델인 프로세스와 스레드의 개념 위에 응용프로그램 도메인이라는 개념을 새롭게 제공합니다.


닷넷 프레임워크 위에서 실행되는 응용프로그램은 JRE의 경우와 마찬가지로 논리적으로 구획이 나뉘어진 하나의 Virtual Machine 위에서 실행되고, 모든 메모리 관리가 이루어지게 됩니다. 생산성 향상을 위하여 이러한 부분을 내부적으로 모두 감추고, 마치 컴파일러나 IL Assembler가 직접 실행 가능한 EXE 파일을 생산해내는것처럼 묘사되어있지만, 우리가 일상적으로 만들어내는 닷넷 컴파일러 기반의 EXE 파일은 미리 프로그래밍된 스텁 코드입니다.


대부분의 경우, 응용프로그램 도메인이 여러 개일 필요 없이, 단순히 프로세스 내에 여러 개의 스레드만을 사용하여 프로그래밍하기 때문에, 스텁 코드의 내용에 대해 고민할 필요가 없습니다. 하지만, 닷넷 프레임워크 실행 환경 안에서 다른 어셈블리를 불러온다는 것은, 기존에 LoadLibrary 같은 Win32 API를 이용하여 DLL을 후기 바인딩하는 것과는 개념적으로 차이가 있습니다.


LoadLibrary의 경우는 Win32 API이며, 메모리를 비롯한 모든 자원 관리가 운영 체제 내의 커널에 의하여 처리됩니다. 하지만 닷넷 프레임워크 환경에서 다른 어셈블리를 로드하고 해제하는 일은 닷넷 프레임워크 환경 내에 위치한 개별 VM에서 처리되는 것이고,로드하고 해제하는 위치 또한 어셈블리를 로드하도록 요구한 코드가 속해있는 응용프로그램 도메인에 고정시키게 됩니다. 그리고, 프로그램의 안정성을 위하여, 한번 로드한 어셈블리를 개별적으로 언로드하는 동작은 Win32 API 때와는 달리 지원되지 않습니다. 이러한 특성 때문에 응용프로그램 도메인을 별도로 분리하여 어셈블리를 필요한만큼 한꺼번에 로드하고, 일괄적으로 해지하는 방식이 나타나게 된 것입니다.


이러한 규칙 아래에서 프로그래밍을 해야 하기 때문에 만약 여러 응용프로그램 도메인을 생성하고 해지하는 일이 빈번한 프로그램을 작성한다면, 다음과 같은 사항들을 고려할 필요가 있습니다.


  • 컴파일러가 제공하는 표준 Stub 프로그램 대신, 직접 Visual C++ 컴파일러로 작성하여 mscoree.dll과fusion.dll에 링크하는 커스텀 Stub 프로그램을 작성하여 프레임워크 응용프로그램을 시작하기
  • 스텁 프로그램을 직접 작성하지 않을 경우, 진입점 메서드에 [LoaderOptimization] 어트리뷰트를 사용하여 다중 응용프로그램 도메인을 관리함을 명시하는 방법


그리고, 응용프로그램 도메인을 이용하여 프로그래밍할 때에는, Global Assembly Cache에 등록할 어셈블리가 아니라 할지라도, 강력한 이름을 어셈블리 내에 부여하여, 어셈블리에 고유한 Identity를 설정하는 것이 좋습니다.


흔히 강력한 이름 (Strong Name)은 어셈블리의 이름, 버전, 언어 및 지역 코드 만으로는 완전하게 식별할 수 없다는 것을 보완하기 위하여, 그리고 다수의 DLL 간의 버전 충돌이 발생하였을 때 DLL을 식별할 여지가 없는 DLL 지옥 (DLL hell)에 빠지는 상황을 예방하기 위하여 도입된 개념입니다만, GAC에 등록을 하던 하지 않던 간에 강력한 이름으로 서명한 어셈블리는 버전 간 충돌에 대한 걱정 없이 "고유한 성격"을 유지할 수있습니다. 그런데, 이 부분이 왜 중요할까요?


간혹 전사적 정책에 따라, Active Directory 등에 연결되어있는 모든 컴퓨터로부터 관리자 권한을 박탈하고, 최소한의 권한만으로 컴퓨터를 액세스할 수 있도록 관리하는 경우가 있는데, 이런 환경에서 GAC는 사용하기 불편한 상태가 됩니다. (GAC의 기본 디렉터리가 %WINDIR%\Assembly에 있으므로 GAC를 변경하는 작업은 "당연하게도" 관리자 권한이 요구됩니다.) 지금 설명할 응용프로그램 도메인이 어셈블리를 찾는 방법은, GAC를 사용할 수 없을 경우, GAC를 대신하여 이용할 수 있는 방법이므로, 강력한 이름을 기초로 식별하는 방법이 유일하기 때문입니다.


닷넷 프레임워크에서 어셈블리를 로드하는 기본 방식은, 응용프로그램 도메인이 시작될 때 설정된 BaseDirectory를 기준으로 아랫쪽에 있는 디렉터리나 파일들을 검색하는 방식입니다. 그러나 이 방법은 편리하지만 매우 제약이 심합니다. 그래서 부수적으로 제공하는 API가 하나 더 있는데, 바로 System.Reflection 네임스페이스의 Assembly 클래스가 노출하는 일부 정적 메서드들 중 From 으로 끝이나는 정적 메서드 시리즈들입니다. 그런데 여기서 고민이 하나 더 생깁니다.


어셈블리를 특정 도메인 위에서 로드하려면, AppDomain 클래스의 메서드를 이용해야 하지만, 앞서 이야기한 규칙에 따르면 자유롭게 로드하기 위해서는 Assembly 클래스의 메서드들을 이용해야 하는 충돌 상황이 발생합니다. 어떻게 이 상황을 풀면 좋을까요? 이 부분에 대해서 많은 Workaround를 찾아다닌 끝에 제가 찾은 방법은 아래와 같습니다.


AppDomain.CreateDomain 메서드를 사용하여 새 응용프로그램 도메인을 만듭니다. 다만, 기존 응용프로그램 도메인과 동일한 Evidence, 유사한 Setting을 상속받을 수 있게 하기 위하여 아래와 같이 매개 변수를 좀 더 구체적으로 써 넣습니다.


1.AppDomainSetup domainSetup = new AppDomainSetup();

2.domainSetup.ApplicationBase = Environment.CurrentDirectory;

3.domainSetup.ConfigurationFile = configPath;

4.domainSetup.PrivateBinPath = privateBinPath;

5.AppDomain domain = AppDomain.CreateDomain(

6.    String.Concat("Delegate Wrapper #", func.GetHashCode()),

7.    null, domainSetup);



만들어진 domain 객체의 SetData 메서드를 사용하여, 응용프로그램 도메인 내에서 참조할 수 있는 데이터를 전달합니다. (함수 수준에서 응용프로그램 도메인 경계를 넘어 직접 매개 변수를 전달할 수 없다는 점에 따른 부분입니다.)


domain.SetData("_ApplicationBasePath", Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "MyData"));


반환 형식이 없고, 매개 변수가 없는 일반 함수를 만들고, 본문 안에서 AppDomain.CurrentDomain 속성을 활용하여 새 도메인 기준으로 코드를 작성해 나갑니다.  때, AppDomain.CurrentDomain.AssemblyResolve 이벤트를 구독하도록 프로그래밍합니다.


AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);


AssemblyResolve 이벤트는 보통의 이벤트 핸들러와는 다르게 이벤트 핸들러가 직접 결과를 반환하는 형태로 구성되어있습니다. 이 지점에서, 응용프로그램 도메인에 전달한 기준 디렉터리 경로를 활용하여 닷넷 프레임워크가 기본 정책에 따라 찾지 못한 어셈블리를 Assembly.LoadFrom 메서드로 따로 로드한 뒤 객체를 되돌려줌으로서 경로 문제를 보완하게 됩니다. 이 때, 로드할 대상 어셈블리에 대해 강력한 이름 서명이 되어있어야 DLL 간 충돌 문제를 완벽히 예방할 수 있습니다.


01.private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)

02.{

03.    string basePath = AppDomain.CurrentDomain.GetData("_ApplicationBasePath"as string;

04.    if (basePath == null || !Directory.Exists(basePath))

05.        return null;

06.    AssemblyName parsedName = new AssemblyName(args.Name);

07.    switch (parsedName.Name)

08.    {

09.        case "My.Common":

10.        case "My.Core":

11.        case "My.Productivity":

12.            return Assembly.LoadFrom(Path.Combine(Path.Combine(basePath, "Library"), String.Concat(parsedName.Name, ".dll")));

13.        default:

14.            return null;

15.    }

16.}


그리고 새로 만든 함수를 domain.DoCallBack 메서드에 콜백 객체로 전달합니다. 만약, 실행 결과를 되돌려 받기 원한다면, domain.GetData 메서드로 콜백 함수 내에서 설정한 데이터를 넘겨받을 수 있습니다. DoCallBack 메서드는 동기 방식 메서드이므로 콜백 메서드의 실행이 끝나기 전까지는 제어권이 되돌아오지 않습니다.


참고로, 콜백 실행 도중 처리되지 않은 예외는 새로 만든 domain의 UnhandledException 이벤트 핸들러를 호출하게 되며, 이 보다 더 직접적으로 예외 처리를 할 필요가 있다면 아래와 같이 TargetInvocationException 형식에 대한 예외를 잡아내어 InnerException을 조사하도록 하면 더 빠르게 예외 처리를 할 수 있습니다.


01.try

02.{

03.    domain.DoCallBack(appDelegate);

04.}

05.catch (TargetInvocationException remoteException)

06.{

07.    if (remoteException != null &&

08.        remoteException.InnerException != null)

09.        throw remoteException.InnerException;

10.    else

11.        throw remoteException;

12.}


도메인의 사용이 모두 끝나면, domain.Unload 메서드를 사용하여 안전하게 도메인에 로드된 모든 어셈블리를 메모리에서 해지합니다.


긴 강좌를 읽어 주셔서 감사합니다. 적절한 상황이나 조건이 그 때 마다 다르겠지만, 프로그램의 규모가 커지고 복잡해  수록 .NET Framework의 숨겨진 이면을 충분히 분석하여 활용하는 것이 중요할 것입니다. 응용프로그램 도메인에 관하여 복잡하게 고민 중이신 분들께 도움이 되었으면 합니다.


출처 : http://www.devpia.com/Maeul/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1823&page=8

Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

未处理 System.InvalidOperationException

  Message=无法关闭撤消单元,因为不存在已打开的单元。


  Source=PresentationFramework

  StackTrace:

       在 MS.Internal.Documents.UndoManager.Close(IParentUndoUnit unit, UndoCloseAction closeAction)

       在 System.Windows.Documents.ImmComposition.CloseCompositionUndoUnit(UndoCloseAction undoCloseAction, ITextPointer compositionEnd)

       在 System.Windows.Documents.ImmComposition.UpdateCompositionText(FrameworkTextComposition composition, Int32 resultLength, Boolean includeResultText, ITextPointer& start, ITextPointer& end)

       在 System.Windows.Documents.ImmComposition.RaiseTextInputStartEvent(FrameworkTextComposition composition, Int32 resultLength, String compositionString)

       在 System.Windows.Documents.ImmComposition.OnWmImeChar(IntPtr wParam, Boolean& handled)

       在 System.Windows.Documents.ImmComposition.ImmCompositionFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)

       在 System.Windows.Interop.HwndSource.PublicHooksFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)

       在 MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)

       在 MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)

       在 System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)

       在 MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)

       在 System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)

       在 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

       在 MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)

       在 MS.Win32.HwndSubclass.DefWndProcWrapper(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

       在 MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)

       在 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

       在 MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)

       在 System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)

       在 System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)

       在 System.Windows.Application.RunDispatcher(Object ignore)

       在 System.Windows.Application.RunInternal(Window window)

       在 System.Windows.Application.Run(Window window)

       在 System.Windows.Application.Run()

       在 LiveChainCHIS_Client.App.Main() 位置 D:\项目\LiveChainCHIS-Client\LiveChainCHIS-Client\obj\x86\Debug\App.g.cs:行号 0

       在 System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)

       在 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)

       在 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()

       在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)

       在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)

       在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

       在 System.Threading.ThreadHelper.ThreadStart()

  InnerException: 


补充说明一下 ,这个错误只出现于 数据绑定后的TextBox控件上。



解决办法:  设置Textbox的UndoLimit 为0就ok。


중문 윈도우에서 중문 IME를 이용해 TextBox에 글자를 입력하려고 하면 발생하는 예외 처리 방법

위에 써있듯이 TextBox에 UndoLimit을 0으로 설정해주면 해결된다


출처 : http://www.cnblogs.com/muzizongheng/p/3169812.html

Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

https://msdn.microsoft.com/en-us/library/hh925568(v=vs.110).aspx

Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

작성 : 김수영(Microsoft Visual C# MVP)

        .NET N'Gene(http://www.dotnetngene.kr)

        훈스닷넷 SYSOP(http://www.hoons.kr)

버전 : 1.0

 

보통은 사용하려는 객체를 모두 초기화 하고 사용한다. 하지만 때에 따라서(메모리 효율 등 여러가지 이유로) 객체가 필요로 하는 시점까지 초기화를 지연하고 있다가 최초 사용하게 되는 시점에 객체를 초기화 하여 사용하기도 한다. 바로 이런 방식을 지연 로딩(Lazy loading : 명확한 의미 전달의 위해 이하 Lazy로 표기 ) 이라고 얘기한다. Lazy loading은 Deferred loading 이라고도 얘기하고 있다. LINQ 관련 문서를 보았다면 Deferred loading이라고 많이 보았을 것이다. Lazy loading과 반대로 사용하는 시점에 바로 초기하는 것을 즉시 로딩(Eager loading : 이하 Eager로 표기) 이라고 한다. Lazy loading, Lazy initialization, Lazy execution, Deferred loading, Deferred execution 다 비슷한 의미라고 보면 될 것이다.

지금 살펴볼 내용은 프로그램 개발시 Lazy와 Eager 중 어떤 방식으로 객체를 초기화 할 것인가에 대한 내용이 아니라 C# 에서의 Lazy loading에 대한 내용, 특히 C# 4.0에서 새롭게 지원하는 내용으로 보도록 하겠다.(Eager loading은 이미 너무나 익숙하므로 더 이상 자세한 언급은 하지 않는다.)

 

[Lazy - Eager]

두가지 패턴 모두 상황게 맞게 사용하면 된다.

 

먼저 Lazy loading은 무엇인가? 위에서 언급 하였듯이 초기화를 바로 하는 것이 아니라 최초 필요한 시점에 초기화가 이루어 지고, 이후에는 생성된 객체를 참조하게 된다.

 

Lazy loading(Initialization)은 보통 다음과 같은 특정을 지니고 있다.

  • 객체 생성시 new를 사용하면 바로 객체가 생성 되므로 팩토리(Factory) 패턴을 이용하여 객체를 생성한다. 그래야 사용을 위한 선언과 실제 객체가 생성되는 시점을 관리 할 수 있다.
  • 한번 생성된 객체는 이후에는 재사용 되므로 싱클톤(Singleton) 패턴을 띠고 있다.
  • 싱클톤 패턴의 유형을 따르므로 멀티스래드 프로그램 작성시에서는 동기화(Sync)에 신경을 써야 한다. 그렇치 않으면 의도 않은 결과를 가져 올 수 있다.
  • 객체 초기화는 첫 요청(혹은 사용되어지는)이 일어나는 시점에 이루어 진다.

 

Lazy에 대한 보다 깊은 개념은 다음 링크에서 참조해 보기 바란다.

 

그럼 .NET에서는 Lazy loading을 어떻게 사용할 수 있고, 어디서 찾아 볼 수 있는지 알아 보도록 하자.

 

1. LINQ

.NET Framework 3.5가 출시되면서 가장 큰 변화를 뽑으라면 바로 LINQ가 아닐까 한다. 바로 LINQ가 대표적인 Lazy Execution 방식이다.

다음 코드를 보자 LINQ 쿼리하는 부분이 존재하고 Select에서 외부 변수값으로 연산을 수행하고 있다.

 

List<int> number = new List<int>{ 1, 2, 3, 4, 5 };

 

int factor = 10;

 

var result = from x in number

               select x * factor;

 

//변수변경

factor = 20;

 

foreach (var item in result)

    Console.WriteLine(item);

 

연산되는 factor 변수의 값은 10일까? 20일까? LINQ쿼리의 실제적인 실행은 선안하는 시점이 아니라 루프문(foreach, while, for 등)이 실행되는 시점에 비로서 처리가 일어 나게 된다. 위 예제에서는 foreach문이 실행되지 직전에 factor 변수의 값이 20으로 변경이 되었으므로 값 20을 가지고 연산이 이루어 졌을 것이다. 그러므로 외부 변수에 영향을 받는 처리는 유의해서 작성해야 한다. 그렇치 않으면 예제처럼 의도하지 않은 결과를 가져 올 수 있다. Lazy에 대한 이해가 없이 작성되었다면 아미도 "쿼리작성 시점에 factor 값이 10으로 이미 처리되고 foreach에서는 단순 출력만 이루어 진다." 라고 착각을 할 수 있다.

 

[LINQ 처리결과]

 

2. Entity Framework 4

데이터 처리를 비즈니스 레벨로 추상화 하여 시스템 유연성을 확보하고 MS SQL 서버만 지원하는 LINQ to SQL과 다르게 이기종 DB(Oracle, DB2 등)를 지원하는 Microsoft의 ORM(Object Relation Mapping) 프레임워크라고 할 수 있다. 엔티티 프게임워크(Entity Framework, 이하 EF)는 .NET Framework 3.5 SP1에서 처음 등장하였으며, 이번 .NET Framework 4에서 많은 개선을 가지고 왔다. 그 중에서 가장 큰 변화가 POCO(Plain Old CLR Object) 클래스 지원과 엔티티의 Lazy loading 지원이다.

쇼핑몰에 회원이 있고, 그 회원의 주문내역이 있다고 가정해 보자. DB에서 아마도 회원테이블과 주문테이블로 정규화 되어 있을 것이다. 그러나 비즈니스 관점에서 데이터를 바라보면 회원을 객체를 생성할 때 (혹은 어디선가 가져올 때) 그와 연관된 주문내역도 같이 보고 싶을 것이다. EF4에서는 context.ContextOptions.DeferredLoadingEnabled 통해 Lazy loading 할지 Eager loading 할지를 선택 할 수 있다.(LINQ to SQL 에서는 DataLoadOptions 으로 처리 가능하다.) Include() 메소드를 통해 첫 쿼리 실행시 모든 SubSet 집합을 다져 오거나 Load() 메소드를 통해 부분적으로 로딩이 가능하다.

한가지 팁으로 MS SQL 쿼리 프로파일러 로 확인하면 각 시점에 어떻게 실행되는지 한눈에 파악 가능하다.

 

EF4에 좀더 알고 싶다면 부족하지만 필자의

를 참고 바란다.

 

[부분적 로딩]

var items = (from x in entity.authors

             where x.au_fname.StartsWith(fname)

             select x).Take(2);

 

Console.WriteLine("SubSet 탐색 – 명지적 로딩");

foreach (authors item in items)

{

    if (item.au_id == "213-46-8915")

    {

        if (!item.titleauthor.IsLoaded)

            item.titleauthor.Load();

 

        foreach (var titleAu in item.titleauthor)

        {

            if (titleAu != null)

                Console.WriteLine("{0} : {1} : {2}"

                    , titleAu.authors.au_fname, titleAu.au_id, titleAu.au_ord);

        }

    }

    else

    {

        foreach (var titleAu in item.titleauthor)

        {

            if (titleAu != null)

                Console.WriteLine("{0} : {1} : {2}"

                    , titleAu.authors.au_fname, titleAu.au_id, titleAu.au_ord);

        }

    }

}

 

[최초 모두 로딩]

var items2 = from x in entity.authors.Include("titleauthor")

             where x.au_fname.StartsWith(fname)

             select x;

 

Console.WriteLine("SubSet 탐색 - Eager Loading");

 

foreach (authors item in items2)

{

    titleauthor tempTitleAu = item.titleauthor.FirstOrDefault();

 

    if (tempTitleAu != null)

        Console.WriteLine(tempTitleAu.au_id);

}

 

Console.WriteLine("============== Trace ==============");

Console.WriteLine(((ObjectQuery)items2).ToTraceString()); 

 

3. Lazy Initialization

이제 제목에서 본 C# 4.0 에서의 Lazy Initialization을 살펴 보자. C# 4.0 & .NET Framework 4에서는 크게 2가지 형태의 Lazy Initialization을 지원하고 있다.

 

  • Lazy<T> (System)
  • LazyInitializer (System.Threading)

 

프레임워크에서 지원하는 인프라를 사용하면 쉽게 Lazy 패턴을 사용할 수 있으며, 특히 까다로운 멀티스래드 프로그램시 동기화 부분을 쉽게 처리 가능하다. LazyInitializer는 네임스페이스가 System.Threading 인 것만 봐도 무엇에 초점을 맞추고 있는지 알 수 있다.

 

3.1 Lazy Initialization 직접 구현

먼저 직접 Lazy Initialization 을 구현을 한번 보자.

사용할 기본 Foo 클래스를 정의해 보자.

 

[Lazy Initialization 할 클래스 정의]

public sealed class Foo

{

    string _Lazy;

 

    public Foo(string lazy)

    {

        this._Lazy = String.Format("{0}({1})", lazy

            , DateTime.Now.ToLongTimeString());

 

        Console.WriteLine("Init Foo -> instance : {0}"this._Lazy);

    }

 

    public void HelloWorld(string value)

    {

        Console.WriteLine("Foo({0}) -> HelloWorld : {1}{2}"

            , this._Lazy, value

            , Environment.NewLine);

    }

}

 

이제 Foo 클래스를 Lazy 방식으로 호출하려면 Foo 객체의 인스턴스를 호출해 주는 Factory가 필요하다. FooObject라는 프로퍼티로 Foo 인스턴스가 없으면 생성을 하고 기존에 생성된 것이 존재하면 있는 인스턴스를 반환하도록 하였다.

 

[Lazy Initialization – Thread Unsafe 방식]

public sealed class LazyLoadingPattern

{

    private Foo _foo;

 

    //Thread Unsafe

    public Foo FooObject

    {

        get

        {

            if (this._foo == null)

            {

                this._foo = new Foo("Lazy Pattern");

            }

 

            return this._foo;

        }

    }

}

 

멀티 스래드 환경에서 호출하여 보자. 2개의 스래드에서 각각 호출 하게 된다.

 

[멀티 스래드에서 호출]

LazyLoadingPattern lazyPatternObj = new LazyLoadingPattern();

 

ArrayList list01 = new ArrayList(2);

list01.Add(lazyPatternObj);

list01.Add("thread01");

Thread thread01 = new Thread(LazyPatternHelloWorld);

 

ArrayList list02 = new ArrayList(2);

list02.Add(lazyPatternObj);

list02.Add("thread02");

Thread thread02 = new Thread(LazyPatternHelloWorld);

 

thread01.Start(list01);

thread02.Start(list02);

 

FooObject에서 Foo 인스턴스 생성시 기존에 생성된 인스턴스를 반환하도록 되어 작성이 되었지만 아래 실행 결과를 보면 각 호출마다 서로 다른 인스턴스가 생성이 되었다. 위와 같은 방식은 멀티 스레드 환경에서 각 생성된 객체에 대한 동기화 문제를 해결 할 수 없다. 멀티스래드에서 동기화는 런타임시 프로그램의 신뢰성에 아주 중요한 문제이다. 아래 결과를 보면 예상했던 결과와는 전혀 다르다. 각각의 스래드가 인스턴스를 생성하고 있다.

 

[Lazy Initialization Thread Unsafe 결과 ]

 

위와 같은 동기화 문제를 해결하기 위해 C# lock 구문을 사용하여 쉽게 처리 가능하다. 이외에 .NET에서는 MutexSemaphore를 통해서도 동기화 처리가 가능하다. 아래 코드에서는 인스턴스 생성하는 부분을 lock 구분으로 감싸주어 동시에 스래드가 접근 하여 인스턴스 생성에 문제가 발생하는 부분을 해결 하였다.

 

[Lazy Initialization – Thread Safe 방식]

public sealed class LazyLoadingPattern

{

    private Foo _foo;

    //Thread Safe

    readonly object _lockObj = new object();

 

    public Foo FooObject

    {

        get

        {

            lock (this._lockObj)

            {

                if (this._foo == null)

                {

                    this._foo = new Foo("Lazy Pattern");

                }

 

                return this._foo;

            }

        }

    }

}

 

lock 구문 사용으로 Foo 인스턴스는 한번만 생성이 되는 것을 볼 수 있다.

 

[Lazy Initialization Thread Safe 결과 ]

 

3.2 Lazy<T>

C# 4.0 에서는 Lazy<T> 라는 새로운 클래스를 통해서 Lazy Initialization을 사용할 수 이으며, 특히 멀티스래드에 안전하게 사용가능하다. 내부적으로 Lazy<T> 클래스는 DCL(double-checked locking) 패턴을 사용한다. 우리가 직접 구현했던 코드에서 문제가 되었던 것은 객체의 인스턴트가 생성되어 있는지 아니면 null 상태인지를 판단하는 부분에서 문제가 발생하는 것이지 이미 인스턴스가 생성되어 있으면 나머지 부분은 큰 문제가 없다. 바로 이 부분을 해결해 주는 것이 DCL 이다. DCL의 핵심은 인스턴스 생성에 대한 처음에만 동기화를 수행하고 인스턴스 생성된 이후에는 확인하지 않는다.(이미 생성된 되었기 때문에 이후에는 전혀 문제가 없다.)

DCL에 대해서는

에서 자세히 볼 수 있다.

Lazy<T> 클래스 생성자에서 사용할 인스턴스 생성 구문을 작성하고 Value 프로퍼티를 통해 접근 가능하다.

 

[Lazy<T> 방식]

//Eager Init

Foo eagerInstance = new Foo("Eager Init");

Console.WriteLine("...01. Working...");

eagerInstance.HelloWorld("Eager");

 

//Lazy Init

Lazy<Foo> lazyInstance = new Lazy<Foo>(() => new Foo("Lazy<T> Init"));

Console.WriteLine("...02. Working...");

lazyInstance.Value.HelloWorld("Lazy 01");

 

결과를 보면 첫번에 Eager 방식은 즉시 인스턴스가 생성된다. 하지만 Lazy<T> 방식의 결과를 보면 인스턴스 생성되는 시점이 첫 호출 될 때인 것을 확인 할 수 있다. (Working… 이란 문자열이 출력되는 시점을 자세히 보라.)

 

[Lazy<T> 결과]

 

3.3 LazyInitializer.EnsureInitialized

특정 객체를 사용하는 것을 굳이 Lazy<T> 클래스의 인스턴스를 통해서 사용하고 싶지 않다면 C# 4.0에서는 또하나의LazyInitializer 정적(static) 클래스를 지원한다. 최초 인스턴스가 필요한 시점에 LazyInitializer. EnsureInitialized 메소드를 통해서 사용하고자 하는 객체의 인스턴스를 생성한다. EnsureInitialized 의 파라미터는 사용하고자 하는 객체의 인스턴스와 인스턴스 생성 팩토리 델리게이트로 되어 있다.

한가지 주의 할 것은 아래 코드에서 보는 것처럼 동일한 인스턴스를 참조하고 있다면(01번과 02번 처럼) 최초 인스턴스 생성 이후의 팩토리 델리게이트는 모두 무시 된다. 실행 결과를 보면 모두 인스턴스는 한번만 생성 되고 하나의 인스턴스를 공유하는 것을 볼 수 있다.

 

[LazyInitializer.EnsureInitialized 방식]

Foo instance = null;

 

//01

LazyInitializer.EnsureInitialized(ref instance

, () => new Foo("Lazy<T> Init 01"));

instance.HelloWorld("01");

 

Thread.Sleep(2000);

 

//02

LazyInitializer.EnsureInitialized(ref instance

, () => new Foo("Lazy<T> Init 02"));

instance.HelloWorld("02");

 

[LazyInitializer.EnsureInitialized 결과]

 

Lazy Initialization 에 대해서 C# 4.0의 새롭게 추가된 클래스 뿐만 아니라 여러 관점에서 살펴 보았다. 어차피 Lazy Initialization은 특정 기술에 종속적인 것이 아니라 디자인 패턴적인 요소이고 그 것을 .NET 에서는 개발자가 보다 쉽게 사용할 수 있다록 지원해 주고 있는 것이다. 필요할 때 적절히~~ 잘~~ 사용하면 된다.

C# 4.0과 .NET Framework 4 에서는 이외에 재미있는 것들이 참 많이 추가 되었다. 이후에 다른 재밌는 요소 더 살펴 보도록 하자.


출처 : 

http://blog.naver.com/PostView.nhn?blogId=dotnethelper&logNo=60104797637&categoryNo=0&parentCategoryNo=0&viewDate=¤tPage=2&postListTopCurrentPage=&isAfterWrite=true

Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
Process.Start(new ProcessStartInfo("explorer.exe", "\"" + @"http://mhchoi8423.tistory.com/" + "\""));


출처 : http://stackoverflow.com/questions/14585709/openning-a-url-containing-a-query-string

Posted by 역시인생한방
,