728x90
반응형
1. 핵심 정리
using System;
using System.Threading;
using system.Threading.Tasks;
class Program {
public static void Foo() {
Console.WriteLine($"Foo : {Thread.CurrentThread.ManagedThreadID}");
Thread.Sleep(3000);
}
public static void Main() {
Thread t = new Thread(Program.Foo);
t.Start();
Task t = Task.Run(Program.Foo);
Console.ReadKey();
}
}
스레드를 만들어 사용하는 코드이다.
위의 코드에서 12~13번 라인의 코드와 15번 라인의 코드는 동일한 작업을 수행한다.
스레드 객체를 만들어 수행하는 것이다.
그렇다면 차이는 무엇일까?
using System;
using System.Threading;
using system.Threading.Tasks;
class Program {
public static void Foo() {
Console.WriteLine($"Foo : {Thread.CurrentThread.ManagedThreadID}");
Thread.Sleep(3000);
}
public static void Main() {
for(int i = 0; i < 20; i++) {
Thread t = new Thread(Program.Foo);
t.Start();
}
// Task t = Task.Run(Program.Foo);
Console.ReadKey();
}
}
한 번 for문을 이용해 20번 반복한다고 해보자.
그렇다면 분명 20개의 스레드가 만들어서 돌텐데
그리고 실행해보면 20개의 ID값도 다 다르고 정상 동작한다.
하지만 가령 CPU가 듀얼코어라면 정말 동시에 실행되고 있는 것은 2개밖에 없을 것이다.
나머지는 왔다갔다 하면서 작업을 수행하게 된다.
그런데 이 왔다갔다 하는 과정에서 성능저하가 있을 수 있다.
그래서 스레드는 무작정 많이 만드는 것보단 적당량을 만들고 또 스레드가 필요하다면
작업을 큐에 넣어놨다가 작업을 마치고 대기중인 스레드를 통해 다시 작업을 수행하게 하는게 좋다.
흔히 그렇게 관리하는것들을 스레드 풀이라고 부른다.
for문 내부는 무작정 스레드 객체를 만드는 것이고
Task는 시스템이 스레드 풀을 만들어 최적의 코드로 작성해준다.
따라서 for문에 Task를 넣고 돌려보면 CPU마다 다르지만 사용했던 스레드가 작업이 끝나면
다시 작업을 수행하는 모습을 볼 수 있다.(ID가 같음)
① Task
=> 스레드 풀(thread pool)에 있는 스레드를 사용해서 메소드 수행
2. 세부 내용
using System;
using System.Threading;
using System.Threading.Tasks;
class Program {
public static void F1() {
Console.WriteLine("F1 Start");
Thread.Sleep(1000);
Console.WriteLine("F2 End");
}
public static void Main() {
Task t = Task.Run(F1);
// t.Wait();
}
}
위의 코드를 실행해보면 어떠한 것도 뜨지 않고 종료되는 것을 볼 수 있다.
왜냐하면 Task는 기본적으로 백그라운드 스레드라 그렇다.
주 스레드가 종료되면 자동적으로 종료되기 때문이다.
Task로 만든 스레드를 대기시키려면 Wait()이란 메소드가 있다.
① 종료를 대기하려면 t.Wait() 사용
using System;
using System.Threading;
using System.Threading.Tasks;
class Program {
public static void F1(object obj) {
Console.WriteLine("F1 Start");
Thread.Sleep(1000);
Console.WriteLine("F2 End");
}
public static void Main() {
Task t = Task.Run(() => F1("hello");
// t.Wait();
}
}
만약 메소드가 인자가 있는 메소드라면 앞선 포스트와 같이
람다 표현식을 사용해서 해결할 수 있다.
② 인자가 있는 메소드라면 람다 표현식 활용
using System;
using System.Threading;
using System.Threading.Tasks;
class Program {
public static void F1(object obj) {
Console.WriteLine("F1 Start");
Thread.Sleep(1000);
Console.WriteLine("F2 End");
}
public static void Main() {
Task t = Task.Run(() => F1("hello");
Console.WriteLine(t.IsCompleted); // false
t.Wait();
Console.WriteLine(t.IsCompleted); // true
}
}
현재 스레드가 동작 중인지 확인하는 방법으로는 IsCompleted가 있다.
③ 스레드가 동작 중인지, 살아있는 스레드인지 확인할 땐 .IsCompleted
using System;
using System.Threading;
using System.Threading.Tasks;
class Program {
public static int F1(object obj) {
Console.WriteLine("F1 Start");
Thread.Sleep(1000);
Console.WriteLine("F2 End");
return 1;
}
public static void Main() {
Task<int> t = Task.Run(() => F1("hello");
Console.WriteLine("Before Result");
Console.WriteLine(t.Result);
Console.WriteLine("After Result");
}
}
만약 반환 타입이 있는 메소드라면 Task<int>로 만들면 된다.
결과 값은 t.Result 멤버로 받을 수 있다.
t.Result를 수행하는 코드가 메소드가 끝나기전에 수행될 수 있는데 걱정할 필요 없다.
t.Result는 메소드가 끝나길 대기하다가 결과를 출력하게 된다.
테스트를 위해 위와 같이 코드를 짜고 돌려보면
Before Result -> F1 Start -> (1초 대기) -> F1 End -> 1 -> After Result
로 나오게 된다.
④ Task vs Task<T>
=> 스레드로 수행할 메소드의 반환 값이 있다면 Task<T> 사용
728x90
반응형
'프로그래밍 > C#' 카테고리의 다른 글
[C#] 쓰레기 수집기 (Garbage Collector) (0) | 2020.05.16 |
---|---|
[C#] Dispose() 메소드 (0) | 2020.05.11 |
[C#] 스레드 클래스 멤버 (0) | 2020.05.08 |
[C#] 스레드 개념 (Thread) (0) | 2020.05.08 |
[C#] Fluent vs Query (LINQ Fluent vs Query Syntax) (0) | 2020.05.07 |