Posts C# Task
Post
Cancel

C# Task

Task


  • TaskThreadPool을 기반으로 작성된 라이브러리이다.

  • 내부적으로 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()과 유사하다.
  • 하나라도 종료되면 종료된 것으로 간주한다.
This post is licensed under CC BY 4.0 by the author.