레이블이 thread인 게시물을 표시합니다. 모든 게시물 표시
레이블이 thread인 게시물을 표시합니다. 모든 게시물 표시

2018년 3월 6일 화요일

C# Task 대충 정리

아래 내용은 아래 책들을 참고하였습니다. 자세한 내용은 아래 책을 참고해 주세요.














Thread

- 필요 자료 전달 가능
- 하지만 결과값을 받기 위해서는 main thread를 block 시켜야 함.
- 예외 발생 시 전파 어려움.

Task

- Thread 보다 높은 수준의 abstraction
- 하나의 concurrent operation을 대표함.
- 일반적으로 main thread가 아닌 thread pool thread에서 비동기적으로 실행 
- Continuation을 통해 Task를 조합할 수 있음.
- TaskComplectionSource와 함께 callback 방식으로 활용가능
- Thread 없이 I/O 한정 연산을 기다리는 것이 가능함.


https://msdn.microsoft.com/ko-kr/library/system.threading.tasks.task(v=vs.110).aspx

using System; using System.Threading; using System.Threading.Tasks; class Example { static void Main() { Action<object> action = (object obj) => { Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj, Thread.CurrentThread.ManagedThreadId); }; // Create a task but do not start it. Task t1 = new Task(action, "alpha"); // Construct a started task Task t2 = Task.Factory.StartNew(action, "beta"); // Block the main thread to demonstrate that t2 is executing t2.Wait(); // Launch t1 t1.Start(); Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId); // Wait for the task to finish. t1.Wait(); // Construct a started task using Task.Run. String taskData = "delta"; Task t3 = Task.Run( () => {Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, taskData, Thread.CurrentThread.ManagedThreadId); }); // Wait for the task to finish. t3.Wait(); // Construct an unstarted task Task t4 = new Task(action, "gamma"); // Run it synchronously t4.RunSynchronously(); // Although the task was run synchronously, it is a good practice // to wait for it in the event exceptions were thrown by the task. t4.Wait(); } } // The example displays output like the following: // Task=1, obj=beta, Thread=3 // t1 has been launched. (Main Thread=1) // Task=2, obj=alpha, Thread=4 // Task=3, obj=delta, Thread=3 // Task=4, obj=gamma, Thread=1

- t1, t2, t3, t4 각각의 방법으로 Task를 만들어서 사용하는 예제
 : t1, t2, t4 는 action으로 정의된 delegate를 실행하는 task
 : t3는 lamda로 정의한 내용을 실행하는 task
- t4의 경우 동기적으로 실행되어 main thread(1번)에서 실행된다.
 : 일반적으로 main (application) thread에서 동기적으로 실행하면 화면이 멈추므로 잘 사용하지 않음.

using System; using System.Threading.Tasks; public class Example { public static async Task Main() { await Task.Run( () => { // Just loop. int ctr = 0; for (ctr = 0; ctr <= 1000000; ctr++) {} Console.WriteLine("Finished {0} loop iterations", ctr); } ); } } // The example displays the following output: // Finished 1000001 loop iterations


- Task.Run()을 사용하여 별도 설정 없이 task를 실행할 수 있음.
- Task.Wait()을 호출하여 task가  끝날때까지 기다릴 수 있고 호출 thread는 block 됨
- Task<TResult> generic type으로 반환값을 지정할 수 있음.

- Task.GetAwaiter()를 사용하여 대기자(awaiter)객체를 반환 받을 수 있고 Awaiter.OnCompleted() 지정하여 실행 후 처리 가능.
 . awaiter.GetResult();는 리턴값이 없을 경우 void임. void라도 불러야 할 필요가 있는게 Task 내에서 exception이 발생할 경우 GetResult()가 exception을 발생 시킴.
 . synchronous context를 전달할 필요가 없을 경우 task.ConfigureAwait(false).GetAwaiter()를 사용하면 전달하지 않음.


TaskCompletionSource

https://msdn.microsoft.com/ko-kr/library/dd449174(v=vs.110).aspx
https://michaelridland.com/xamarin/taskcompletionsource-xamarin-beautiful-async/

- 적절한 시기에 실행할 수 있는 Task를 생성할 수 있다.
- 좀 말이 어려운데 간단하게 말하면 Task를 한번 감싸는 객체이고 Task 실행을 적절하게 조절할 수 있음.
   결과를 .SetResult(), .SetCanceled(), .SetException()를 사용하여 caller에게 전달할 수 있음.


using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; class TCSDemo { // Demonstrated features: // TaskCompletionSource ctor() // TaskCompletionSource.SetResult() // TaskCompletionSource.SetException() // Task.Result // Expected results: // The attempt to get t1.Result blocks for ~1000ms until tcs1 gets signaled. 15 is printed out. // The attempt to get t2.Result blocks for ~1000ms until tcs2 gets signaled. An exception is printed out. static void Main() { TaskCompletionSource<int> tcs1 = new TaskCompletionSource<int>(); Task<int> t1 = tcs1.Task; // Start a background task that will complete tcs1.Task Task.Factory.StartNew(() => { Thread.Sleep(1000); tcs1.SetResult(15); }); // The attempt to get the result of t1 blocks the current thread until the completion source gets signaled. // It should be a wait of ~1000 ms. Stopwatch sw = Stopwatch.StartNew(); int result = t1.Result; sw.Stop(); Console.WriteLine("(ElapsedTime={0}): t1.Result={1} (expected 15) ", sw.ElapsedMilliseconds, result); // ------------------------------------------------------------------ // Alternatively, an exception can be manually set on a TaskCompletionSource.Task TaskCompletionSource<int> tcs2 = new TaskCompletionSource<int>(); Task<int> t2 = tcs2.Task; // Start a background Task that will complete tcs2.Task with an exception Task.Factory.StartNew(() => { Thread.Sleep(1000); tcs2.SetException(new InvalidOperationException("SIMULATED EXCEPTION")); }); // The attempt to get the result of t2 blocks the current thread until the completion source gets signaled with either a result or an exception. // In either case it should be a wait of ~1000 ms. sw = Stopwatch.StartNew(); try { result = t2.Result; Console.WriteLine("t2.Result succeeded. THIS WAS NOT EXPECTED."); } catch (AggregateException e) { Console.Write("(ElapsedTime={0}): ", sw.ElapsedMilliseconds); Console.WriteLine("The following exceptions have been thrown by t2.Result: (THIS WAS EXPECTED)"); for (int j = 0; j < e.InnerExceptions.Count; j++) { Console.WriteLine("\n-------------------------------------------------\n{0}", e.InnerExceptions[j].ToString()); } } } }

- tcs1은 t1 task의 결과로 1초 뒤 15값을 t1.Result로 전달함.
- tcs2와 같이 task에서 발생된 exception도 task의 결과로 전달 가능하다.


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

멀티 쓰레드를 사용하는 근본적인 이유들
- Main application thread를 차단(block)하지 않기 위해
- I/O를 완료할 때까지 기다리지 않고 다른 작업을 함께 처리
- 동시 처리해야하는 작업들의 수행


Synchronous operation : 수행 후에 실행의 흐름을 caller에게 반환하는 연산
Asynchronous operation : 할 일을 다 마치기 전에 실행의 흐름을 caller에게 반환한다.


비동기 프로그래밍
- 오래 실행되는 함수는 asynchronous operation 형태로 작성
- 예전에는 동기적인 함수를 작성하고 이 함수를 thread에서 실행했던 것과는 달리 함수 내부에서 동시성을 시작함.
 . thread에 묶이지 않고 동시성을 구현할 수 있어 효율적임.
 . worker thread를 관리가 줄어들고 thread 안정성이 향상될 수 있음.
 . thread에서 분리되어 처리되던 기존 방식에 비해 세밀하게 동시성을 처리할 수 있음.


. Synchronous operation

int DoLongWork(int start)
        {
            // Do Long Work
            return start;
        }

        void DisplayResult()
        {
            for (int i = 0; i < 10; i++)
                Console.WriteLine($"Long work { i } started, result = { DoLongWork(i) }");

            Console.WriteLine("Done");
        }

(결과)
Long work 0 started, result = 0
Long work 1 started, result = 1
Long work 2 started, result = 2
Long work 3 started, result = 3
Long work 4 started, result = 4
Long work 5 started, result = 5
Long work 6 started, result = 6
Long work 7 started, result = 7
Long work 8 started, result = 8
Long work 9 started, result = 9


. Asynchronous operation

Task<int> DoLongWorkAsync(int start)
        {
            return Task.Run(() =>
            {
                // Do Long Work
                return start;
            });
        }

        void DisplayResult2()
        {
            for (int i = 0; i < 10; i++)
            {
                var awaiter = DoLongWorkAsync(i).GetAwaiter();
                awaiter.OnCompleted(() =>
                {
                    Console.WriteLine($"Long work { i } started, result = { DoLongWork(i) }");

                });
            }
            Console.WriteLine("Done");
        }


DoLongWorkAsync() 10개가 모두 병렬로 실행됨.

(결과)
Done
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10

Done이 표시되고 Long work들이 표시 될 수 있음.
그리고 캡쳐링된 i가 10인 상태에서 모두 실행되므로 i를 지역변수로 캡쳐링 하면 정상적으로 나옴.

void DisplayResult2()
        {
            for (int i = 0; i < 10; i++)
            {
                int captured = i;
                var awaiter = DoLongWorkAsync(i).GetAwaiter();
                awaiter.OnCompleted(() =>
                {
                    Console.WriteLine($"Long work { captured } started, result = { DoLongWork(captured) }");

(결과)
Long work 1 started, result = 1
Long work 0 started, result = 0
Done
Long work 3 started, result = 3
Long work 2 started, result = 2
Long work 4 started, result = 4
Long work 5 started, result = 5
Long work 7 started, result = 7
Long work 6 started, result = 6
Long work 8 started, result = 8
Long work 9 started, result = 9


연속적으로 진행하고자 한다면

public void DisplayResult3()
        {
            DisplayResultFrom(0);
        }

        void DisplayResultFrom(int i)
        {
            var awaiter = DoLongWorkAsync(i).GetAwaiter();
            awaiter.OnCompleted(() =>
            {
                Console.WriteLine($"Long work { i } started, result = { DoLongWork(i) }");

                if (i++ < 10)
                    DisplayResultFrom(i);
                else
                    Console.WriteLine("Done");
            });
        }

DisplayResult 자체를 비동기화하여
DisplayResult에서 작업 완료 시 신호를 보내는 작업 객체를 돌려주게 하려면

Task DisplayResultAsync4()
        {
            var display = new LongWorkDisplay();
            display.DisplayResultFrom(0);
            return display.Task;
        }

        public class LongWorkDisplay
        {
            TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
            public Task Task { get => _tcs.Task; }

            public void DisplayResultFrom(int i)
            {
                var awaiter = DoLongWorkAsync(i).GetAwaiter();
                awaiter.OnCompleted(() =>
                {
                    Console.WriteLine($"Long work { i } started, result = { awaiter.GetResult() }");

                    if (i++ < 10)
                        DisplayResultFrom(i);
                    else
                    {
                        Console.WriteLine("Done");
                        _tcs.SetResult(null);
                    }
                });
            }
        }


하지만 C# 비동기 함수 기능을 사용하면 다음과 같음.

        public async Task DisplayResultAsync5()
        {
            for (int i = 0; i < 10; i++)
                Console.WriteLine($"Long work { i } started, result = { await DoLongWorkAsync(i) }");

            Console.WriteLine("Done");
        }


예외 전달
async void ButtonClick(object sender, EventArgs args)
{
    await Task.Delay(1000);
    throw new Exception("exception");
}



이 때 ButtonClick 실행은 await문을 넘어간 후 메세지 루프로 돌아오므로
얼마 지나서 발생한 예외는 메세지 루프의 catch 블록에 잡히지 않는다고 함.

하지만 synchronous context가 있는 경우 AsyncvoidMethodBuilder는
void async 함수에서 미처리 예외를 잡아서동기화 문맥에 전달한다.

비동기 함수에서 바로 처리가 가능한 경우 result를 받을 수 있다면
다음과 같이 처리할 수 있다고 함.

var awaiter = GetWebPageAsync().GetAwaiter();
if (awaiter.IsCompleted)
    Console.WriteLine(awaiter.GetResult());
else
    awaiter.OnCompleted(() => ...);


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

async 및 await를 사용한 비동기 프로그래밍  

비동기 프로그래밍의 필요성과 예제 시나리오를 지원하기 위해 동기적, 비동기적 프로그램을 예시를 들어 설명하고 있어 개념이 이해 안된다면 한번 읽어보세요.

2014년 5월 1일 목요일

Regarding Tizen native REST client app development (HTTP, JSON parser, Event-driven thread)

아래 내용은 Tizen 2.2 기반이라 상위 버전에서는 별 의미 없는 내용입니다.
--------------------------------------------------------------------------- Tizen native app으로 REST client app을 개발하고자
관련된 tutorial 링크 및 참고 사항들을 대충 정리 함...

- HTTP request/response 처리
- JSON parsing
- HTTP response 처리를 위한 Event-driven thread 사용

[HTTP transaction concept]

https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/guide/net/http_transaction.htm

REST client app을 만드니... 기본적인 HTTP transaction에 대해서는 이해가 필요하겠지..

 . HTTP transaction 에 대한 내용들
http://www.pearsonhighered.com/assets/hip/us/hip_us_pearsonhighered/samplechapter/0672324547.pdf



http://www.w3.org/2002/Talks/www2002-p3p/all.htm

 . Chunked transfer
  : 간단히 말하면 일반적인 http streaming transfer는 content를 response로 한번에 보내지만 chunked transfer는 content를 임의의 크기로 쪼개서 보냄. 이런 경우는 server측의 performance의 제약이나 전달되는 content가 실시간 생산되는 content의 경우(크기를 예측할 수 없음) 이런 방법을 사용.
  : Tizen HTTP transfer default mode는 Non chunked transfer mode 이지만 일반적인 server에서는 chunked transfer 지원.
  : http://en.wikipedia.org/wiki/Chunked_transfer_encoding

[HTTP request/response]


[HttpClient Sample Overview]
: https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/sample_descriptions/httpclient.htm



HTTP request를 전송하고 response의 body bytes 수를 표시하는 sample app.
tizen.org 를 대상으로 HTTPS request를 실행하므로 외부 network으로 연결에 문제가 없는 지 확인 필요. (proxy 같은)

[HTTP Client]
https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/tutorials/net_tutorial/task_httpclient.htm

* HTTP request 시 http transaction을 asynchronous 하게 처리하기 위해 아래 IHttpTransactionEventListener 등록이 필요

Tizen::Net::Http::IHttpTransactionEventListener
: https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.apireference/classTizen_1_1Net_1_1Http_1_1IHttpTransactionEventListener.html

  • OnTransactionAborted
  • OnTransactionCertVerificationRequestedN
  • OnTransactionCertVerificationRequiredN
  • OnTransactionCompleted
  • OnTransactionHeaderCompleted
  • OnTransactionReadyToRead
  • OnTransactionReadyToWrite


HTTP-GET 시나리오에서
no chunked transfer에서는 response header 전달 이후 전체 body가 전송되므로 다음과 같은 이벤트가 발생할 것이고

Client   event                                                                         Server
                                                 GET request >
 HeaderCompleted event        < response header
 Completed event                   < response body

chunked transfer에서는 response header 전달 이후 body가 나눠서 오므로 다음과 같은 이벤트가 발생할 것이다.

Client    event                                                                        Server
                                                 GET request >
 HeaderCompleted event        < response header
 ReadyToRead event               < response body
 ReadyToRead event               < response body
 ReadyToRead event               < response body
 Completed event                   < response body

HTTP-POST 시나리오는 아마도 이렇지 않을까? (해보지는 않음.)

Client     event                                                                       Server
                                          POST request header >
ReadyToWrite event                  request data >
ReadyToWrite event                  request data >
ReadyToWrite event                  request data >
ReadyToWrite event                  request data >
 Completed event                   < response body


[주의 및 참고]

* HTTP transfer를 app에서 사용 하려면 http privilege가 필요
  . manifest.xml > privileges > http://tizen.org/privilege/http

* HttpSession의 copy constructor가 private 인 관계로 reference를 사용할 수 없음

* 일반적인 얘기겠지만 HttpSession의 instance를 http transaction 중 유지하지 않으면 listener 호출 중 crash 발생됨.

* Http request 시 특정 사용자 정보를 response callback에서 확인하려면 HttpTransaction의 SetUserObject()를 사용하라 HttpTransaction instance는 request, response시 모두 접근할 수 있음.
 : https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.apireference/classTizen_1_1Net_1_1Http_1_1HttpTransaction.html#a8eff3be989218969838e76310cf99706


[HTTP programming guide]

나머지 항목들은 한번 보시라.

The most common uses of HTTP connectivity are the following:
https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/guide/net/http_connectivity.htm


[JSON]


Introduction to JSON on Tizen
https://developer.tizen.org/documentation/articles/introduction-json-on-tizen

JSON Parser App
https://developer.tizen.org/dev-guide/2.2.0/?topic=%2Forg.tizen.native.appprogramming%2Fhtml%2Ftutorials%2Fweb_tutorial%2Ftask_jsonparserapp.htm

이 예제에서는 JSON data를 parsing해서 key, value list를 제공해 주는 예제다.
단지 JSON data인 string을 parsing해서 탐색 가능한 data structure로 제공해 준다는 의미만 있을 뿐.. 그 이상의 편의는 제공하지 않는다.
사용자가 json response를 직접 parsing 해서 원하는 value를 가져오거나 utility 함수를 만들어서 key에 맞는 value를 가져오도록 해야 할듯.

   // Call JSON parser
   IJsonValue* pJson = JsonParser::ParseN(buf);

   __pJsonKeyList->RemoveAll(true);
   __pValueList->RemoveAll(true);
   TraverseFunction(pJson);


void
JsonForm::TraverseFunction(IJsonValue* pValue)
{   
   switch (pValue->GetType())
   {
      case JSON_TYPE_OBJECT:
      {
         JsonObject* pObject = reinterpret_cast< JsonObject* >(pValue);
         IMapEnumeratorT<const String*, IJsonValue*>* pMapEnum = pObject->GetMapEnumeratorN();

         while (pMapEnum->MoveNext() == E_SUCCESS)
         {

...
 case JSON_TYPE_STRING:
      {
         JsonString* pVal = reinterpret_cast< JsonString* >(pValue);
...
 case JSON_TYPE_ARRAY:
      {
         JsonArray* pJsonArray = static_cast< JsonArray* >(pValue);
         pJsonArray->GetCount();
         IEnumeratorT<IJsonValue*>* pEnum = pJsonArray->GetEnumeratorN();
         while (pEnum->MoveNext() == E_SUCCESS)
...
...

[Alternatives JSON parsers]

JsonCpp
: boost library 기반, library 생성을 위한 컴파일 필요
: http://jsoncpp.sourceforge.net/

cJSON
: simple, MIT license, 2013년이 최신 업데이트
: http://sourceforge.net/projects/cjson/

jsmn
: simple, MIT license, 아직 개발중으로 보임 2014년 최근까지 수정 내역 있음.
: https://bitbucket.org/zserge/jsmn/overview


다른 대안을 찾아 보니 위의 것들이 있었고 http://www.json.org/ 중간에 보면 각 언어에 맞는 json parser library들이 존재하니 알아서 취사 선택하면 될듯함.

간단하게 사용하고자 cJSON을 사용하였고 사실 Tizen에서 제공하는 JSON parser와 기능은 거의 동일하지만 c style로 api 사용 및 array 처리가 간단하다.
JsonCpp도 많이 사용하고 편리한 사용성의 api들을 제공하지만 boost library를 사용하므로 boost library의 cross compile이 필요하고 소스의 양도 좀 되서 그냥 패스함.


cJSON을 통해 parsing하면 cJSON* 이 리턴되고 이를 사용해서 key에 맞는 value들을 뽑아 내면 됨.

typedef struct cJSON {
struct cJSON *next,*prev;     /* next/prev allow you to walk array/object chains.
                                                      Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */

struct cJSON *child;             /* An array or object item will have a child pointer pointing to
                                                        a chain of the items in the array/object. */


int type; /* The type of the item, as above. */

char *valuestring; /* The item's string, if type==cJSON_String */
int valueint; /* The item's number, if type==cJSON_Number */
double valuedouble; /* The item's number, if type==cJSON_Number */

char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;


일반적이 primitive value들은 list traversal 방법으로 확인하면 되며 array나 object들은 아래 함수를 사용해서 뽑아내면 된다.

/* Get Array size/item / object item. */
int          cJSON_GetArraySize(cJSON *array)
cJSON *cJSON_GetArrayItem(cJSON *array,int item)
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string)


cJSON* element = null;
element = cJSON_GetObjectItem(item, "peoples");
if(null != element)
{
int size = cJSON_GetArraySize(element);
for(int i = 0 ; i < size ; i++)
{
cJSON* child_element = cJSON_GetArrayItem(element, i);
AppLog("peoples (%d) = %s - %s", i, child_element->string, child_element->valuestring);
...
}
element = null;
}

[Thread]

https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/guide/base/thread.htm

Tizen에서는 아래와 같은 두가지(Main thread 제외)의 thread model들이 사용할 수 있고 각각의 특색은 다음과 같음.

[Event-driven Thread]
https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.apireference/classTizen_1_1Base_1_1Runtime_1_1EventDrivenThread.html
: Event-driven threads run based on events and they execute in a loop until they receive an event notification to terminate. Event-driven threads allow using asynchronous calls.

[Worker Thread]
https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.apireference/classTizen_1_1Base_1_1Runtime_1_1Thread.html
: Worker threads run linearly; they only execute the main body of the thread and exit. Asynchronous calls cannot be used, since the thread does not run an event-loop.




주기적인 HTTP request/response를 처리하고자 할 경우 Event-driven thread를 사용해야 함. 단순 http request/reponse를 처리할 때는 상관없으나 loop을 사용할 경우 worker thread에서는 바로 error가 발생하는 것으로 보인다. 실제 tizen platform에서 처리하는 방법은 알 수 없으나 주기적으로 반복해야 하는 상황에서는 event driven thread를 사용하는게 필요 함.

사용상의 별 특이점은 없음. 예제대로 만들면 잘 돌아감.. ㅎ