책을 보면서 독학을 하다가 나중에 까먹거나 헷갈릴거 같은 개념들을 적어둘 목적으로 글을 써보았다.
스레딩
System.Threading.EventWaitHandle
EventWaitHandle은 Monitor 타입처럼 스레드 동기화 수단의 하나다. 스레드로 하여금 이벤트를 기다리게 만들 수 있고, 다른 스레드에서는 원하는 이벤트를 발생시키는 시나리오에 적합하다.
이때 이벤트 객체는 딱 두 가지 상태만 갖는데, 바로 Signal과 Non-Signal로 나뉘고 서로 간의 상태 변화는 Set, Reset 메서드로 전환할 수 있다.
Set : Non-Signal → Signal / Reset : Signal → Non-Signal |
이와 함께 이벤트 객체는 WaitOne 메서드를 제공한다. 어떤 스레드가 WaitOne 메서드를 호출하는 시점에 이벤트 객체가 Signal 상태이면 메서드에서 곧바로 제어가 반환되지만, Non-Signal 상태였다면 이벤트 객체가 Signal 상태로 바뀔 때까지 WaitOne 메서드는 제어를 반환하지 않는다. 즉, 스레드는 더는 실행되지 못하고 대기 상태로 빠지는 것이다.
using System;
using System.Threading;
using System.IO;
namespace prac1
{
class Program
{
int number = 0;
static void Main(string[] args)
{
// Non-Signal 상태의 이벤트 객체 생성
// 생성자의 첫 번째 인자가 false이면 Non-Signal 상태로 시작
// true이면 Signal 상태로 시작
EventWaitHandle ewh =
new EventWaitHandle(false, EventResetMode.ManualReset);
Thread t = new Thread(threadFunc);
t.IsBackground = true;
t.Start(ewh);
// Non-Signal 상태에서 WaitOne을 호출했으므로 Signal 상태로 바뀔 때까지 대기
ewh.WaitOne();
Console.WriteLine("주 스레드 종료!");
}
static void threadFunc(object inst)
{
EventWaitHandle ewh = inst as EventWaitHandle;
Console.WriteLine("2초 후에 종료");
Thread.Sleep(2000);
Console.WriteLine("스레드 종료!");
// Non-Signal 상태의 이벤트를 Singal 상태로 전환
ewh.Set();
Console.WriteLine("는 훼이크~!");
}
}
}
threadFunc 메서드에서 ewh.Set() 으로 Signal 상태로 전환한다 해도 메서드가 끝나버리는 건 아니다. Signal 상태로만 바뀌는거고 메서드에 있는 코드가 다 끝난 후에 다시 메인문으로 전환된다.
비동기 호출
using System;
using System.Threading;
using System.IO;
using System.Collections;
using System.Text;
namespace prac1
{
class Program
{
int number = 0;
static void Main(string[] args)
{
using (FileStream fs =
new FileStream(@"C:\windows\system32\drivers\etc\HOSTS", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
byte[] buf = new byte[fs.Length];
fs.Read(buf, 0, buf.Length);
string txt = Encoding.UTF8.GetString(buf);
Console.WriteLine(txt);
}
}
}
}
- 여기서 FileStream.Read 메서드는 동기 호출에 속한다. 즉, Read 메서드는 디스크의 파일로부터 데이터를 모두 읽기 전까지는 제어를 반환하지 않는다. 이 때문에 다른 말로 동기 호출을 블로킹 호출(blocking call)이라고도 한다.
- 쉽게 말해 느린 디스크 I/O가 끝날 때까지 스레드는 아무 일도 못한다는 것이고, 이는 곧 CPU가 일을 하지 않고 놀게 된다는 것을 의미한다.
- 이러한 동기 호출의 단점을 해결하기 위해 비동기 호출이 제공된다. FileStream은 비동기 호출을 위해 Read/Write 메서드에 대해 각각 BeginRead/EndRead, BeginWrite/EndWrite 메서드를 쌍으로 제공한다.
비동기 방식의 파일
using System;
using System.Threading;
using System.IO;
using System.Collections;
using System.Text;
namespace prac1
{
class Program
{
static void Main(string[] args)
{
FileStream fs =
new FileStream(@"C:\windows\system32\drivers\etc\HOSTS", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
FileState state = new FileState();
state.Buffer = new byte[fs.Length];
state.File = fs;
fs.BeginRead(state.Buffer, 0, state.Buffer.Length, readCompleted, state);
// BeginRead 비동기 메서드 호출은 스레드로 곧바로 제어를 반환하기 때문에
// 이곳에서 자유롭게 다른 연산을 동시에 진행할 수 있다.
//Console.ReadLine();
int result = 0;
for (int i = 0; i < 99999999; i++)
{
result = i;
}
Console.WriteLine(result);
fs.Close();
}
static void readCompleted(IAsyncResult ar)
{
FileState state = ar.AsyncState as FileState;
state.File.EndRead(ar);
string txt = Encoding.UTF8.GetString(state.Buffer);
Console.WriteLine(txt);
}
}
class FileState
{
public byte[] Buffer;
public FileStream File;
}
}
- BeginRead 메서드는 디스크로부터 파일 데이터를 읽어낼 때까지 기다리지 않고 곧바로 스레드에 제어를 반환한다. 따라서 스레드는 이후의 코드를 끊김없이 실행할 수 있다.
위의 캡쳐화면을 보면 파일을 읽어오는 동안 for문에서 연산이 동시에 이루어지는 걸 확인할 수 있다.
System.Delegate의 비동기 호출
일반적으로 비동기 호출은 입출력 장치와의 속도 차이에서 오는 비효율적인 스레드 사용 문제를 극복하는데 사용된다. 그런데 닷넷에서는 특이하게도 입출력 장치뿐만 아니라 일반 메서드에 대해서도 비동기 호출을 할 수 있는 수단을 제공하는데, 다름 아닌 델리게이트가 그러한 역할을 한다. 즉, 메서드를 델리게이트로 연결해 두면 이미 비동기 호출을 위한 기반이 마련된 것이나 다름없다.
using System;
using System.Threading;
using System.IO;
using System.Collections;
using System.Text;
namespace prac1
{
class Program
{
public delegate long CalcMethod(int start, int end);
static void Main(string[] args)
{
CalcMethod calc = new CalcMethod(Calc.Cumsum);
long result = calc(1, 100);
Console.WriteLine(result);
}
}
public class Calc
{
public static long Cumsum (int start, int end)
{
long sum = 0;
for (int i = start; i <= end; i++)
{
sum += i;
}
return sum;
}
}
}
- 다음 코드에서 calc 델리게이트 수행은 당연히 현재의 스레드에서 수행된다.
- 하지만 델리게이트의 비동기 호출을 위한 메서드 (BeginInvoke / EndInvoke)를 사용하면 calc 인스턴스에 할당된 Calc.Cumsum 메서드의 수행을 ThreadPool의 스레드에서 실행할 수 있다.
'C# > Study' 카테고리의 다른 글
[복습]애트리뷰트 - Attribute, C# (0) | 2021.01.01 |
---|---|
[복습] Reflection - C#, 리플렉션 (0) | 2020.12.28 |
[복습] 인덱서 - C# (0) | 2020.12.21 |
[복습] LINQ - C#, 링크 ,링큐 (0) | 2020.12.18 |
[복습] Delegate - C#, CSharp, 씨샵, 델리게이트, 대리자 (0) | 2020.12.15 |