Thread
Sync
Monitor.Enter()
과 Monitor.Exit()
을 사용하면 그 구간에는 한 스레드만이 진입해서 실행할 수 있다. 이는 lock
Context 로 축약해서 사용할 수 있다.
Interlocked.Exchange()
등을 통해 단순한 유형의 Atomic 연산을 할 수 있다.
Semaphore
도 존재하며 Cross-Process Synchronization 이 필요없다면 SemaphoreSlime
을 사용할 것이 권장된다1.
신호처리 매커니즘을 사용한다면 AutoResetEvent
, ManualResetEvent
을 통해 동기화를 편하게 할 수 있다. 이때 후자의 경우 Reset()
한 Thread 만 WaitOne()
에서 깨어날 수 있다. 단 깨어나기 전에 Reset()
을 또 호출하면 아무 의미가 없어 카운팅같은 건 Semaphore 를 써야한다.
Async
Thread 로 직접 함수를 호출해도 비동기적 작업이 가능하다. 그럼에도 비동기적 함수가 필요한 이유는 편의성 이외에도 하나가 더 있다.
예를들어 I/O 처리를 생각해보자. 이는 크게 I/O 준비, I/O 처리, I/O 후처리 부분으로 나뉘어진다. 만약 Thread 를 새로 생성해 비동기적인 동작을 구현한다면 위의 모든 구간이 다른 Thread 에서 동작할 수도 있다. 하지만 보통 I/O 준비 구간은 다른 Thread 에서 동작할 필요가 없다. 그래서 BeginRead()
나 ReadAsync()
같은 I/O 처리 함수가 비동기적으로 수행한 후에 I/O 후처리도 호출해주면 Thread 에 대한 부담이 줄어들게 된다.
과거에는 delegate 에서 지원하는 BeginInvoke()
를 사용하는 패턴을 썼지만 .Net Framework 에만 지원된다. 지금은 Task
를 이용하는 TAP 패턴 을 쓰도록 권장한다.
Task (C# 4.0)
TPL(Task Parallel Library) 에 속한 타입이다. 기존의 ThreadPool.QueueUserWorkItem()
에서는 어려웠던 Thread 동기화나 리턴값 반환이 가능해져 더 편리해졌다.
Task.Factory.StartNew<>()
를 통해 객체를 만들지 않고 바로 생성할 수도 있다.
Task 는 단순히 Thread 를 경량화 한 것이라고 생각할 수 있는데 그렇지 않다. Threads execute Tasks which as scheduled by a TaskScheduler2.
async / await (C# 5.0)
asnyc
키워드는 await
를 예약어로 컴파일러가 인식하게한다. 그리고 리턴 타입을 void
. Task
, Task<T>
로 제한시킨다.
Task<T>
의 경우 마치 원래 리턴타입이T
인 것처럼 구현해야한다.- 리턴 타입이
Task
의 경우await
구문이 있어야 한다. - 리턴 타입이
void
의 경우 함수 내에서await
를 쓸 수 있다. 하지만 예외가 발생 시try...catch
로 처리할 수 없어 EventHandler 같은 특수한 경우 외에는 자제된다3.
await
는 async
식별자가 있을 때에 한해서 Task
에 대해서 적용된다. 그 이하의 코드가 Task
가 끝나고 동작하도록 컴파일러에 의해 코드가 변경된다는 의미를 갖는다.
- 이때
await
이후 코드가 같은 Thread 에서 동작하라는 보장이 없다. 예를들어 UI Thread 에서는await
이후의 코드가 UI Thread 에서 동작하도록 되어있다. 그래서await
가 포함된async
함수에 대해서Task.Wait()
를 수행하면 DeadLock 이 발생한다. - 그래서 현재 수행되는 Thread 가 어떻게 처리하는지가 중요한데 다음을 참고하자.
ValueTask (C# 7.0)
ValueType<T>
를 사용하면 Method 내에서 await
를 호출하지 않는 경우 Task
를 만들지 않아 성능을 높힌다. C# 5.0 에도 Task
를 상속받아 구현하면 가능한 기능이긴 했다.
AsyncEnumerable (C# 8.0)
기존의 yeild
Context 에서는 비동기 처리를 할 수가 없다. 왜냐하면 IEnumerable<T>
는 async
의 리턴타입 제약과 맞지 않기 때문이다.
이에 비동기 스트림을 이라는 별명을 가진 IAsyncEnumerable<T>
가 추가되어 yeild
Context 도 async
가 가능해졌다. 그리고 이를 위해 await foreach
예약어가 추가되었다.
댓글남기기