ref 와 out 차이

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

ref 는 메소드 호출 전에 초기화 해야되고


out 은 메소드 내에서 반드시 한번은 값을 할당해야한다


속도(Performance) 테스트 결과 out 을 쓴 경우가 더 빠른 것으로 나타남 (.NET 4.0 기준)

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

Debug.Assert and Debug.Fail  (0) 2015.12.24
다른 컴퓨터 정보 가져오기  (0) 2015.12.21
async, await를 이용한 비동기 프로그래밍  (0) 2015.09.17
Aggregate() vs string.Join()  (0) 2015.04.09
C# Using PostMessage  (0) 2015.03.29
Posted by 역시인생한방
,
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 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

[Server - C#]


using System;

using System.IO;

using System.IO.Pipes;


namespace NamedPipeCs

{

class Program

{

static void Main(string[] args)

{

while (true)

{

using (var server = new NamedPipeServerStream("DavidPipe"))

{

server.WaitForConnection();


using (var reader = new StreamReader(server))

{

string temp;

while ((temp = reader.ReadLine()) != null)

{

Console.WriteLine("{0}", temp);

}

}

}

}

}

}

}


----------------------------------------------------------------------------


[Client - C++]


#include <iostream>

using namespace std;

#include <windows.h>


int main()

{

HANDLE hPipe = CreateFile("\\\\.\\pipe\\DavidPipe", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)

return -1;


LPTSTR lpMessage = TEXT("FUCK THAT SHIT");

DWORD dwNumberOfBytesToWrite = (lstrlen(lpMessage) + 1) * sizeof(TCHAR);

DWORD dwNumberOfBytesWritten;


BOOL bSuccess = WriteFile(hPipe, lpMessage, dwNumberOfBytesToWrite, &dwNumberOfBytesWritten, NULL);

if (bSuccess == FALSE)

return -1;


CloseHandle(hPipe);


return 0;

}

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

string.Join() 이 더 빠르다


자세한 사항은 아래에서 확인

출처 : http://blog.naver.com/vactorman/80208961985

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

ref 와 out 차이  (0) 2015.12.02
async, await를 이용한 비동기 프로그래밍  (0) 2015.09.17
C# Using PostMessage  (0) 2015.03.29
Random Class  (0) 2015.02.08
SynchronizationContext 가 추구하는 동기화 #1  (0) 2015.02.07
Posted by 역시인생한방
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
    [DllImport("user32.dll")]
    static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);

    const uint WM_USER_CUSTOM = WM_USER + 8423;

    public void Post()
    {
        // Get Processes
        var processes = Process.GetProcessesByName("notepad.exe");
// Main part foreach (Process p in processes) if (p.ProcessName == "notepad.exe") { PostMessage(p.MainWindowHandle, WM_USER_CUSTOM, 0, 0);
} }


출처 : http://stackoverflow.com/questions/6927431/c-sharp-using-postmessage

참고 : http://xarfox.tistory.com/45

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 크기의 광고 코드만 넣을 수 있습니다.

우선 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 크기의 광고 코드만 넣을 수 있습니다.

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

Posted by 역시인생한방
,