Task.ContinueWith



 Task 기반 비 동기 작업을 수행하다가 보면 순차적으로 수행해야 하는 작업이 있을 수 있다. 그럴때마다 await 키워드로 다시 비동기를 실행하지 않고 ContinueWith를 사용하면 순차적인 비 동기를 수행 할 수 있다. 아래 구문을 살펴 보자.


public async void TAP_AsyncAwait_TestMethod2()
{
    //.Net framework 4.5에 추가된 메소드로 
    //Task.Factory.StartNew 래핑한다.
    var returnValue = await Task.Run(() => { /* Something */ return 10; })
        //10을 리턴하면 아래 구문을 수행 한다.
        .ContinueWith(_result => { return _result.Result + 100; })
        //110을 리턴하면 아래 구문을 수행 한다.
        .ContinueWith(_return => { Thread.Sleep(1000); return _return.Result + 200; })
        //1초 후 310을 리턴하면 아래 구문을 수행 한다.
        //결과 값을 String으로 반환 한다.
        .ContinueWith(_return => { return _return.Result.ToString(); });
 
    Console.WriteLine(returnValue);       
}

[코드1] ContinueWith로 비 동기 순차 실행


 이와 같이 사용하여 순차적인 비 동기 실행을 구현하여 await를 남발하지 않도록 하면 될 것이다.



소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략하도록 하겠습니다. 포스트의 내용이 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 하기 위해 노력하였습니다.. 실제 개발에서도 필요한 소스는 단순히 Copy & Paste 만으로도 사용할 수 있습니다. 그리고 주석을 이용해 nDoc이나 별도의 자동 Document 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다. 
※ DOC에 대한 프로그램 정보 Util link

ing™       





Task.Run() 메소드


 .Net framework 4.5로 업그레이드 되면서 기존에 사용하고 있던 Task.Factory.StartNew() 메소드가 다음의 구문으로 쉽게 사용할 수 있도록 추가 되었다. 

//Factory를 통해 비 동기 Task를 반환
Task.Factory.StartNew(() => { /* Something */ });
 
//.Net framework 4.5에 추가된 메소드로 
//Task.Factory.StartNew 래핑한다.
Task.Run(() => { /* Something */ });

[코드1] 비 동기 실행 Task 수행 코드


 물론 위 코드도 Generic을 지원하고 있으며 .Net framework 4.5에서는 간결하게 래핑한 Run() 메소드를 사용하는걸 권장한다. 다만 일반 사용 보다는 정교하게 비 동기 작업에 대해서 컨트롤을 해야 할 때는 기존과 같은 방법으로 Factory의 StartNew를 사용해서 사용해야 할 것이다.



소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략하도록 하겠습니다. 포스트의 내용이 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 하기 위해 노력하였습니다.. 실제 개발에서도 필요한 소스는 단순히 Copy & Paste 만으로도 사용할 수 있습니다. 그리고 주석을 이용해 nDoc이나 별도의 자동 Document 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다. 
※ DOC에 대한 프로그램 정보 Util link

ing™       




C# Task-based Asynchronous Pattern - TAP(2)

async / await keyword

http://msdn.microsoft.com/ko-kr/library/hh873175.aspx

http://msdn.microsoft.com/en-us/library/hh873175.aspx


http://www.albahari.com/threading/part2.aspx#_Thread_Safety


2013/02/26 - [.Net Framework] - [Async]Task-based Asynchronous Pattern - TAP(1) Async #7


 이번 포스트는 지난 시간에 이어 TAP에 대해서 더 깊이 있게 알아 볼 수 있는 시간을 가져 보도록 하겠습니다. 지난 포스트의 마지막 부분에서 외부 시스템에서 정보를 가져오는( I/O, Network, ...)을 통해 예제를 통해 비 동기로 실행하면 총 수행 시간을 절약할 수 있는 방안에 대해 알아 보았다. 이 번에는 .Net framework 4.5가 나오면서 C#이 5.0으로 업그레이드가 되었다. 그러면서 비 동기 컨트롤을 지원하는 키워드인 async/await가 추가 되어 더욱 편리하고 안전하게 사용할 수 있게 되었다. 이제 새로 추가된 기능으로 비 동기 구현을 해 보도록 하자. 

/// <summary>
/// async/await 키워드를 이용해서 비 동기 실행
/// </summary>
[TestMethod]
public async void TAP_AsyncAwait_TestMethod1()
{
    //비 동기 실행을 하지만 결과값이 리턴이 될 때까지 대기 한다.
    var returnValue = await Task.Factory.StartNew(() => 
        {
            Debug.WriteLine("TAP_AsyncAwait_TestMethod1 비 동기 실행 시작");
            Thread.Sleep(1000);
            Debug.WriteLine("TAP_AsyncAwait_TestMethod1 비 동기 실행 종료");
            return 10;
        });
 
    //정상 수행 여부 체크
    Assert.AreEqual(10returnValue);
}

[코드1] async/await로 비 동기 실행

(테스트 프로젝트에서는 정상적으로 실행 되지 않아 아래 Console에서 확인 한다)


class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        p.TAP_AsyncAwait_TestMethod1();
 
        Console.ReadKey();
    }
 
    public async void TAP_AsyncAwait_TestMethod1()
    {
        //비 동기 실행을 하지만 결과값이 리턴이 될 때까지 대기 한다.
        var returnValue = await Task.Factory.StartNew(() =>
        {
            Console.WriteLine("TAP_AsyncAwait_TestMethod1 비 동기 실행 시작");
            Thread.Sleep(1000);
            Console.WriteLine("TAP_AsyncAwait_TestMethod1 비 동기 실행 종료");
            return 10;
        });
 
        //값 확인
        Console.WriteLine(returnValue);
            
    

[코드2] Console에서 async/await로 비 동기 실행


 "코드2"에서와 같이 TAP_AsyncAwait_TestMethod 함수의 리턴 타입 앞에 'async' 키워드를 이용해서 이 함수는 비 동기로 수행 되는 메소드라고 알려줘야 한다 이렇게 해야지만 메소드 내부에서 await를 사용할 수 있다. 그리고 메소드 내부에서 비 동기로 수행되는 구문에서 await를 이용해서 결과값을 받아 오면 된다. 이렇게 간단한 규칙만으로 비 동기 실행을 절차적인 모양(기존 절차지향 언어에서 비 동기를 구현 할 때 Context를 맞춰줘야 하는 복잡한 지식 없이 절차 지향의 구문의 모양처럼 작성 하면서 메소드의 동작 방식은 비 동기로 구현 한다)으로 작성 하도록 도와 준다. 한번 직접 실행하여 분석해 보도록 하자. 생각 보다 쉽게 비 동기 지원 메소드를 작성 하고 사용할 수 있다. 


 이 기능은 프레임워크에서 지원되는 기능이 아닌 컴팔일러 단에서 지원하는 기능이며 내부적으로 TAP(1)에 설명된 사항 처럼 Task 객체를 이용해서 비 동기를 구현 하도록 내부적으로 구현 한다. .Net framework 4.5에서는 이렇게 쉽게 비 동기를 지원하는 편리한 기능을 추가 하였다. 그렇지만 모든 메소드를 비 동기로 작성해야 하는 필요성은 없으며 I/O, Network, DB와 같이 상대적으로 느린 시스템에서 데이터를 컨트롤 하는 부분에서만 비 동기를 사용할 것을 적극 권장하는 바이다.

C# Task-based Asynchronous Pattern - TAP

http://msdn.microsoft.com/ko-kr/library/hh873175.aspx

http://msdn.microsoft.com/en-us/library/hh873175.aspx


http://www.albahari.com/threading/part2.aspx#_Thread_Safety



 Task-based Asynchronous Pattern(이하 TAP)은 .Net framework 4.0에서 나온 개념으로 저 수준의 Thread를 컨트롤하지 않고 고 수준의 Task 레벨에서 비동기를 지원하고 있고 이것을 이용해 비동기 패턴 구현을 권장하고 있다.

 TAP은 단일 메소드를 사용하여 비동기 작업의 시작과 완료를 나타냅니다.이는 APM(Begin, End)과 대조적이다. TAP 메소드는 반환되는 형식에 따라 Task, Task<T>로 반환되고 Task<T>로 반환된 객체는 Result 필드를 통해서 반환된 값을 사용할 수 있다. 우선 간단하게 Task를 이용해서 비 동기 실행 코드를 살펴 보도록 하자. 

/// <summary>
/// TAP 비 동기 수행 테스트
/// </summary>
[TestMethod]
public void TAP_TestMethod_01()
{
    //간단한 비 동기 실행 수행
    Task task = new Task(() => 
    {
        Thread.Sleep(1000);
        Debug.WriteLine("비 동기 실행.");
    });
 
    //타스크를 비동기 실행 시킨다.
    task.Start();
 
    //타스크가 완료가 될 때까지 대기 한다.
    task.Wait();
 
    Debug.WriteLine("비 동기 실행 완료.");
 
    //강제로 성공이라고 표시한다.
    Assert.IsTrue(true);
}

[코드1] Task를 이용해 비 동기 수행 


 "코드1"을 보면 Task를 인스턴스 시키면서 파라메터로 Action을 넘겨준다. 그리고 인스턴스에서 Start()를 호출하여 비 동기로 작업을 수행한다. task.Wait()는 비 동기 작업이 완료되기 전에 메소드를 종료 하는것을 방지 하기 위해 대기하도록 설정하였다. 위와 같은 방법은 이전의 Thread와 별 다를 바 없을 것이다. 그렇다면 다음 코드를 한번 보도록 하자.

/// <summary>
/// Task의 Factory를 통해 TAP 비 동기 수행 테스트
/// </summary>
[TestMethod]
public void TAP_TestMethod_02()
{
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(1000);
        Debug.WriteLine("비 동기 실행.");
    });
 
    //타스크가 완료가 될 때까지 대기 한다.
    task.Wait();
 
    Debug.WriteLine("비 동기 실행 완료.");
 
    //강제로 성공이라고 표시한다.
    Assert.IsTrue(true);
}

[코드2] Task의 Factory를 통해 Task 생성 후 실행


 "코드2"는 "코드1"과 다른점이 task.Start() 없이 곧바로 수행이 되도록 하고 있다. 그럼 다시 다음 코드를 보고 비교 해  보도록 하자. 

/// <summary>
/// Task<![CDATA[<T>]]>의 Factory를 통해 TAP 비 동기 수행 테스트
/// </summary>
[TestMethod]
public void TAP_TestMethod_03()
{
    var tempValue = 100;
 
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(1000);
        Debug.WriteLine("비 동기 실행.");
 
        return 100;
    });
 
    //비 동기 실행에서 값이 반활 될때까지 대기하고 
    //반환이 되면 returnValue에 값을 바인딩 한다.
    var returnValue = task.Result;
 
    Debug.WriteLine("비 동기 실행 완료.");
 
    //값이 같으면 성공
    Assert.AreEqual(returnValuetempValue);
}

[코드3] Task<T>형식으로 비 동기 작업 수행 테스트


 "코드3"에서는 지금까지와는 다른 task.Result를 사용하였다. 이 구문은 비 동기로 수행되는 작업이 완료되어 값을 반환 할 때까지 대기하고 값을 할당 해준다. 그런데 왜 비동기를 사용해야 할까? 비 동기를 사용 하면 낮설고, 교착 상태에 빠질 수도 있으며 디버깅 하기도 더 어렵다. 그렇다면 아래 "코드4"를 한번 보길 바란다.

/// <summary>
/// Task<![CDATA[<T>]]>의 Factory를 통해 TAP 비 동기 수행 테스트
/// 여러 타스크를 비 동기로 수행하여 시간 절약을 얻었다.
/// </summary>
[TestMethod]
public void TAP_TestMethod_04()
{
    var tempValue = 450;
 
    Stopwatch sw = new Stopwatch();
    sw.Start();
 
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task1 = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(2000);
        Debug.WriteLine("DB1.Server.com에서 DB를 가지고 옴.");
 
        return 50;
    });
 
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task2 = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(3500);
        Debug.WriteLine("Socket로 레거시 시스템에서 정보를 가져 옴");
 
        return 150;
    });
 
    //Task의 Factory를 통해서 타스크를 받아 오는 방법
    var task3 = Task.Factory.StartNew(() =>
    {
        Thread.Sleep(1500);
        Debug.WriteLine("DB2.Server.com에서 DB를 저장하고 영향 받은 row 갯수를 가져옴.");
 
        return 250;
    });
 
    //비 동기 실행에서 값이 반활 될때까지 대기한다.
    Task.WaitAll(new[] { task1task2task3 });
 
    sw.Stop();
    Debug.WriteLine(sw.ElapsedMilliseconds);  //3504밀리초가 output에 찍혔다.
    Debug.WriteLine("비 동기 실행 완료.");
 
    //returnValue에 비 동기로 수행해 받은 값을 바인딩 한다.
    var returnValue = task1.Result + task2.Result + task3.Result;
 
    //값이 같으면 성공
    Assert.AreEqual(returnValuetempValue);
}

[코드4] 비 동기 수행으로 전체 수행 시간을 빠르게 할 수 있다.


 위 코드를 살펴 보면 외부 시스템(DB, Socket)으로 정보를 가져오고 저장하는 프로세스가 있다. 각 수행 시간이 2000, 3500, 1500 밀리초가 걸린다고 가졍하며 코딩을 하였다. 이 작업을 순차적으로 수행하면 2000 + 3500 + 1500을 모두 합한 7초가 걸린다는 산술적인 계산이 떨어 진다. 그렇지만 위 작업을 비 동기로 수행하면 최대 공약수인 3500밀리초만 걸리다는 것을 알 수 있다. 위 코드에서 sw.ElapsedMilliseconds 코드의 결과값이 3504(컴퓨터 마다 약간의 차이가 발생한다)로 측정이 되었다. 7초와 3.5초라는 단순 비교 만으로도 비 동기 수행을 함으로써 얻을 수 있는 잇점을 장황한 설명을 하지 않아도 알 수 있을 것이다.



Tip!

나중에 ASP.NET MVC에서 IAsyncController을 통해서 비동기 컨트롤을 사용하여 얻을 수 있는 잇점에 대해서도 상세하게 다루도록 하겠다.




 지금까지 TAP를 이용해서 비 동기 작업에 대해서 간단하게 탐색해 보았다. 다음 포스트에서는 지금보다 더 상세하게 알아보는 시간을 가져보도록 하겠다.


------------------------------------------------------------------------------------------


 아래는 MS에서 배포하고 있는 TAP관련 문서이며 번역번도 같이 올려 놓는다. - 번역본은 판교 (이전 가락동)에서 교육을 받을 때 강습생들이 번역한 문서다.


TAP(Task-based Asynchronous Pattern).docx

TAP(Task-based Asynchronous Pattern)(번역본)completed.docx


C# Asynchronous Programming Patterns

http://msdn.microsoft.com/en-us/library/jj152938.aspx

 

 요즘은 앱 전성 시대인거 같다. iOS를 기점으로 시작한 앱 개발 경쟁이 안드로이드와 Windows Phone으로 까지 번기게 되며 이제는 기존 어플리케이션도 앱과 같은 형식으로 배포를 하려하는 움직임까지 보이고 있다. 더군다나 이제는 MS Office 제품군에서도 내부 확장을 앱 형식으로 확장해서 사용할 수 있도록 지원해주고 있다. 예로 Outlook 2013에서 사용할 수 있는 앱을 개발하면 C/S와 Web에서 같은 앱을 사용할 수 있도록 지원해 주고 있다.

 

 이와 같이 앱 개발 전성시대와 함께 중요하게 대두된 것이 사용자 경험에서 중요한 변화가 시작이 되었다. 끊김없는 UI, 미려한 화면, 즉각적인 반응 등이 EndUser에게 선택되는 중요한 요점이 되었다. 프로그래머 입장에서는 비동기 프로그램 방식으로 위와 같은 형식으로 개발하여 사용자 경험에 대해 지원 해 줄 수 있다.

 

 .Net에서는 Async 개발 지원을 "Asynchronous Programming Pattern"을 통해 가이드를 해 주고 있다. 아래 리스트는 MS에서 가이드 해주는 패턴이다.

- Asynchronous Programming Model (APM)

Event-based Asynchronous Pattern (EAP)

- Task-based Asynchronous Pattern (TAP)

 (http://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2287)

 

아래는 간단하게 패턴 유형 별 코드를 비교해 보도록 하겠다.

 

Sync Pattern

public class MyClassAsync
{
    public int Read(byte[] bufferint offsetint count);
}

 

APM

public class MyClassAPM
{
    public IAsyncResult BeginRead(
        byte[] bufferint offsetint count,
        AsyncCallback callbackobject state);
    public int EndRead(IAsyncResult asyncResult);
}

 

EAP

public class MyClassEAP
{
    public void ReadAsync(byte[] bufferint offsetint count);
    public event ReadCompletedEventHandler ReadCompleted;
}

 

TAP - 추천 하는 방법 ( .Net framework 4.0 이상 지원 )

public class MyClassTAP
{
    public Task<int> ReadAsync(byte[] bufferint offsetint count);
}

 

여기에 추가적으로 .Net Framwork 에서는 async / await 패턴이 추가 되었다.

public class MyClassAsyncAWait {     public async ReadAsync(byte[] bufferint offsetint count);  }

 

 이와 같이 .Net framework 에서는 비동기 프로그래밍에 대해서 보다 쉽고, 개발자의 수고를 덜 수 있도록 점점 진화된 비동기 프로그래밍 개발을 지원해 주고 있다.

 

다음 포스트 각 패턴에 따르는 예제 코드를 하나 하나씩 짚어 보도록 하겠다.

+ Recent posts