Task
-
Task
는ThreadPool
을 기반으로 작성된 라이브러리이다. -
내부적으로
ThreadPool
의 스레드 개수를 차지하며,ThreadPool
의 스레드 개수 제한에 영향을 받는다. -
가벼운 비동기 작업에 특화되어 있다.
-
Thread.Abort()
처럼 간단히 종료할 수 없다. -
작업이 완료된
Task
객체는 다시 수행될 수 없다.
1. 생성, 실행, 대기
[1] 개별 객체 정의
-
스레드 바디로 사용될 메소드를 미리 정의한다.
-
정의한 메소드 또는 람다식을 이용해
Task
객체를 만들고, 시작시킨다. -
이렇게 시작되는 Task는 새로운 스레드에서 실행된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void TaskBody()
{
// Some Codes..
}
private void MainMethod()
{
Task t1 = new Task(TaskBody); // 미리 정의한 메소드 사용
Task t2 = new Task(() => { /* Codes.. */ }); // 람다식 사용
// 각 Task 객체를 새로운 스레드에서 실행
t1.Start();
t2.Start();
// Task들의 종료 대기
t1.Wait();
t2.Wait();
// 또는
// Task.WaitAll(t1, t2);
}
.RunSynchronously()
메소드를 통해 실행 및 대기를 한 번에 수행할 수 있다.
1
2
3
4
5
private void MainMethod()
{
Task t = new Task(TaskBody);
t.RunSynchronously(); // = Start() + Wait()
}
[2] Task.Run()을 통해 즉시 실행
Task.Run()
메소드를 통해 실행하고, 리턴되는Task
객체를 받아올 수 있다.
1
2
3
4
5
private void MainMethod()
{
Task t = Task.Run(TaskBody);
t.Wait();
}
[3] Task.Factory()을 통해 즉시 실행
Task.Run()
과 유사하다.
1
2
3
4
5
private void MainMethod()
{
Task t = Task.Factory.StartNew(TaskBody);
t.Wait();
}
2. 작업의 결과 받아오기
-
매개변수가 존재하지 않고 리턴타입이 존재하는 메소드를 정의하고, 스레드바디로 사용한다.
-
.Result
프로퍼티를 통해 작업의 결과를 참조할 수 있다. -
명시적으로
.Wait()
메소드를 호출하지 않아도,.Result
를 호출한 지점에서 해당Task
의 종료를 대기한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private string TaskWithReturn()
{
Console.WriteLine("Start");
Thread.Sleep(1000);
Console.WriteLine("End");
return "RETURN";
}
private void MainMethod()
{
// 작업 시작
Task<string> t = new Task<string>(TaskWithReturn);
t.Start();
// 작업 대기 및 결과 참조
Console.WriteLine(t.Result);
Console.WriteLine("Main Thread End");
}
3. 연계 작업 등록하기
-
.ContinueWith()
메소드를 통해,Task
종료 시 연이어 수행할 작업을 등록할 수 있다. -
.ContinueWith()
메소드는 연이어 수행되는 작업의 핸들을Task
로 리턴한다. -
부모 스레드에서 이를 대기할 경우 연계 작업까지 모두
.Wait()
를 해줘야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void TaskBody()
{
Console.WriteLine("Start");
Thread.Sleep(1000);
Console.WriteLine("End");
}
private void MainMethod()
{
// 작업 시작
Task t = new Task(TaskBody);
// t 종료 시 연이어 실행할 작업 등록
Task t2 = t.ContinueWith((Task tt) =>
{
Console.WriteLine("Start - T2");
Thread.Sleep(1000);
Console.WriteLine("End - T2");
});
t.Start(); // t 실행
t2.Wait(); // t2 대기
Console.WriteLine("Main Thread End");
}
4. 작업 취소하기
-
Thread
와는 달리,Task
를 통한 작업은 강제로 종료할 수 없다. -
대신 작업 대기(Wait)를 취소할 수는 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private string TaskWithReturn()
{
Console.WriteLine($"Start");
Thread.Sleep(3000);
Console.WriteLine($"End");
return "RETURN";
}
private void CancelTask(CancellationTokenSource cts)
{
Thread.Sleep(1000);
cts.Cancel();
}
private void MainMethod()
{
// 작업 시작
Task<string> t = new Task<string>(TaskWithReturn);
t.Start();
// 다른 스레드에서 작업 종료하도록 예약
CancellationTokenSource cts = new CancellationTokenSource();
Task.Run(() => CancelTask(cts));
// 메인 스레드에서 작업 대기
// + 취소 시 발생하는 예외 처리
try
{
t.Wait(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Wait Canceled");
}
Console.WriteLine("Main Thread End");
//Console.WriteLine(t.Result);
//Thread.Sleep(5000);
//Console.WriteLine("Main End");
}
-
굳이 작업의 중도 종료가 필요하다면, 작업 중에 일정 주기로 종료 여부를 확인하는 방식을 사용해야 한다.
-
중도 확인이 불가능한 작업이라면, 취소도 불가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void CancelTask(CancellationTokenSource cts)
{
Thread.Sleep(1000);
cts.Cancel();
}
private CancellationTokenSource cts = new CancellationTokenSource();
private void CancellableTask()
{
for (int i = 0; i < 50 && !cts.IsCancellationRequested; i++)
{
Console.WriteLine($"Task : {i}");
Thread.Sleep(100);
}
}
private void MainMethod()
{
// 작업 시작
Task t = new Task(CancellableTask);
t.Start();
// 다른 스레드에서 작업 종료하도록 예약
Task.Run(() => CancelTask(cts));
// 메인 스레드에서 작업 대기
// + 취소 시 발생하는 예외 처리
try
{
t.Wait(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Wait Canceled");
}
Console.WriteLine("Main Thread End");
}
Task.Run()
의 두 번째 인자로 CancellationToken
을 넣을 수 있지만, 이걸 취소시킨다고 해도 이미 시작된 Task
가 강제로 종료되지는 않는다.
API 정리
동적 메소드
.Start()
- 새로운 스레드에서 해당 작업을 실행시킨다.
.Wait()
- 호출하는 스레드에서 대상 Task의 종료를 기다린다.
.RunSynchronously()
- 호출하는 스레드에서 대상 Task를 실행하고 종료를 기다린다.
Start()
+Wait()
와 같다.
.ContinueWith(Action<Task>)
- 해당 작업이 종료되면 연이어 수행할 작업을 등록한다.
.Dispose()
- Task의 리소스를 메모리에서 해제한다.
동적 프로퍼티
.Result
- 결과를 리턴하는 경우에만 사용한다.
- 작업의 결과값을 받아온다.
.Status
- 현재 Task의 상태
.IsCompleted
- 작업이 정상적으로 완료되었는지 여부
.IsCanceled
- 작업이
CancellationToken
에 의해 종료되었는지 여부
.IsFaulted
- 처리되지 않은 예외로 인해 종료되었는지 여부
정적 메소드
Task.Run(Action)
- 새로운 스레드에서 해당 작업을 실행시킨다.
Task.Run<TResult>(Func<TResult>)
- 새로운 스레드에서 해당 작업을 실행시킨다.
- 결과를 리턴하고,
.Result
로 받아올 수 있다.
Task.WaitAll(params Task[])
- 호출하는 스레드에서 지정한 모든 Task가 종료되기를 기다린다.
Task.WaitAny(params Task[])
- 호출하는 스레드에서 지정한 모든 Task 중 하나라도 종료되기를 기다린다.
- 종료되는 Task가 있을 경우, 대기를 중지한다.
Task.WhenAll(params Task[])
- 매개변수로 지정한 Task들을 한번에 관리할 수 있는 Task를 리턴한다.
- 반환된 Task 객체는 상태 확인 또는 Wait 용도로 사용된다.
Task.WhenAny(params Task[])
- WhenAll()과 유사하다.
- 하나라도 종료되면 종료된 것으로 간주한다.