C# 5: แนะนำให้รู้จัก keyword ใหม่สำหรับ Asynchronous Functions

แนะนำให้รู้จัก Asynchronous Functions กันก่อน

C# 5 ใส่ keyword ใหม่คือ await และ async เพื่อสนับสนุนการเขียน code ให้ทำงานในรูปแบบ asynchronous  ได้ง่ายเข้าไปอีก

รูปแบบงาน asynchronous  ก็คือ มีงานอยู่งานหนึ่งที่เราจะปล่อยให้ไปทำจนเสร็จได้ โดยที่เราไม่ต้องรอ ซึ่งเราก็จะสร้างงานอีกอย่างหนึ่งเพื่อให้มารอแทนผลลัพย์ตอบกลับมาแทนเรา ส่วนเราก็ทำอย่างอื่นต่อไปเลย นี่เรียกว่า รูปแบบงาน asynchronous ในแบบตรงข้ามกัน เรียกว่ารูปแบบงาน synchronous คือ งานเดียวกันนั้นละ แต่มันจะบร็อคเราไว้ให้รอจนมันทำเสร็จแล้วคืนผลลัพย์กลับมาก่อน ถึงจะปล่อยให้เราทำงานอื่นต่อไปได้

รูปแบบงาน asynchronous ถูกประยุกต์ใช้กับงานในลักษณ์ต้องการปล่อยหลายๆงานให้ทำไปพร้อมๆกัน(concurrency) ซึ่งงานจะถูก parallel ให้ประมวลผลใน CPU ซึ่งเหมาะมากกับสมัยนี้ที่ CPU มีหลาย Core หรือประมวลผลบนกลุ่มของ Cloud เพิ่มขึ้นเรื่อยๆ ซึ่งการประยุกต์ใช้ รูปแบบงาน asynchronous โปรแกรมเมอร์ก็จะแจกงานหลายๆงานเพื่อให้ทำพร้อมๆกันโดยผ่าน multithreading งานทึ่ถูกแตกนี้เพื่อประมาลผล logic ในที่นี้จะเรียกว่า CPU-bound และโปรแกรมเมอร์ ก็จะต้องสร้างอีกงานไว้คู่กันเพื่อรอผลลัพย์ตอบกลับมาด้วย(callback function) ซึ่งกรณีนี้จะเรียกว่า I/O- bound นั่นเอง

เอาละเรามาค่อยๆทำความเข้าใจจากรูปแบบงาน synchronous กันก่อนเพราะเป็นแบบปกติที่เราใช้กันอยู่แล้ว ข้างล่างนี้เป็น code เพื่อคำนวณง่ายๆ แต่ใช้เวลานาน เพื่อให้รู้สึกว่าต้องรอนานหน่อย หรือก็คือส่วนของ CPU-bound

int ComplexCalculation()
{

double x = 2;
for (int i = 1; i < 100000000; i++) x += Math.Sqrt (x) / i;
return (int)x;

}

ข้างล่านนี้เป็น code ของ client หรือผู้ใช้ ที่แสดงการเรียกใช้งาน ComplexCalculation ในแบบ synchronous

int result = ComplexCalculation();
// หลังจากเสร็จงานแล้ว
Console.WriteLine (result); // 116

เราจะเห็นว่า เมื่อเราเรียกใช้งาน ComplexCalculation เราจะถูกหยุดไว้ตรงนั้นก่อนจนกว่า การคำนวณของเราจะเสร็จ แล้วคืนค่าออกมาเก็บไว้ที่ result เราถึงจะแสดงผลออกมาได้

เอาละเรามาดูวิธีเรียกงานในแบบ asynchronous กันบ้าง

โชคดีที่ CLR ได้กำหนด class Task<TResult> ซึ่งมันอยู่ใน System.Thread.Tasks เพื่อใช้กับแนวคิด asynchronous ไว้ให้เราแล้วครับ เราเพียงแค่สร้าง Task<TResult> สำหรับ CPU-bound โดยการเรียก Task.Run เพื่อที่จะไปบอกให้ CLR นั้น run งานนี้ให้เราโดย CLR จะส่งงานนี้ไปให้แต่ละ thread เพื่อให้ทำงานได้ในแบบพร้อมๆกัน(parallel) อีกที ตามนี้

Task<int> ComplexCalculationAsync()
{

return Task.Run (() => ComplexCalculation());

}

Method ข้างบนนั้นเป็นงานในรูปแบบของ asynchronous ลำดับต่อไปเราก็จะต้อง สร้างงานอะไรบ้างอย่างเพื่อที่จะมานั้งรอผลที่คำนวณเสร็จแล้วแทนเรา(callback function) คืองานของ I/O-bound นั้นเอง ง่ายอีกแล้วละครับกับเจ้างานที่มารอรับผลที่เสร็จแล้วแทนเรา โดย Task<TResult> แก้ปัญหานี้ให้เราโดยใช้ GetAwaiter method ของมันเท่านั้นเองครับ ง่ายมากตาม code ข้างล่างนี้

Task<int> task = ComplexCalculationAsync();
var awaiter = task.GetAwaiter();
awaiter.OnCompleted (() =>
{

int result = awaiter.GetResult();
Console.WriteLine (result); // 116

});

จาก code เราจะเห็นได้ว่า ใช้ method GetAwaiter จาก  Task<TResult> ที่คืนมาจาก asynchronous method จะได้ awaiter ที่จะมาทำหน้าที่รอผลแทนเรา หลังจากนั้นก็ผูกงานที่เราต้องการทำต่อไปหลังจากที่ได้ผลตอบกลับมาแล้วให้กับ awaiter ด้วย OnCompleted โดยงานส่วนนี้จะเพียงแค่แสดงผลออกมาเท่านั้น

ข้างตนนี้เป็นวิธีการเรียกงานรูปแบบ asynchronous แบบเดิมอยู่นะครับต่อไปนี้จะเป็นการใช้ keyword ใหม่ของ C#5 กันละครับ ดูกันต่อไปเลย

Keyword await และ async

keyword await ช่วยให้เราเขียน code เพื่อรอผลงานในลักษณะของ asynchronous น้อยลง และอ่านง่ายขึ้นมาก รูปแบบทีเขียนโดยให้ keyword await 

var result = await expression;
statement(s);

ซึ่งก็มีอะไรเหมือนๆกับรูปแบบนี้

var awaiter = expression.GetAwaiter();
awaiter.OnCompleted (() =>
{

var result = awaiter.GetResult();
statement(s);

);

เราจะเห็นว่ามันง่ายขึ้นมากเลยใช่มั้ยละครับ เอาละเมื่อเราเอาไปใช้กับตัวอย่างที่กล่าวมาข้างต้นแล้ว เราก็จะได้รูปแบบการเขียน code ที่อ่านง่าย ลด code ลง ซึ่งเราสามารถเขียน await ได้ใน scope ของ asynchronous functionได้เลย ยกเว้นใน block ของ catch หรือ finaly, lock และ ในบริบทของ unsafe ได้แบบนี้

async void Test()
{

for (int i = 0; i < 10; i++)
{

int result = await ComplexCalculationAsync();
Console.WriteLine (result);

}

}

ต่อไปนี้จะเป็นการเขียน asynchronous functions ด้วย keyword async จาก function ComplexCalculationAsync เมื่อเขียนด้วย  keyword async จะได้ code หน้าตาแบบนี้

async Task<int> ComplexCalculation()
{

return await Task.Run<int>(() =>
{

double x = 2;
for (int i = 1; i < 100000000; i++) x += Math.Sqrt(x) / i;
return (int)x;

});

}

ซึ่งเราจะเห็นว่า keyword async มันทำให้ function แบบที่เราเขียนปกติแปลงให้เป็นรูปแบบของ asynchronous function ได้ง่ายมาก แก้ไขแทบจะเหมือน function เดิม และวิธีใช้งาน asynchronous function นี้เราก็สามารถเขียนในรูปแบบของ synchronous ได้เหมือนเดิมแบบนี้

var task = ComplexCalculation();
Console.WriteLine(task.Result);

ง่ายมากเลยใช่มั้ยละครับ เมื่อเราใช้ async เขียน function ใดๆเราก็จะได้ function นี้ที่สามารถเรียกได้ในแบบ asynchronous หรือ synchronous ได้โดยเขียน code น้อยลง และอ่านง่ายขึ้น

สุดท้ายแล้วละครับ เรามาเขียน code งานในรูปแบบของ parallel เพื่อให้ run งานนี้พร้อมๆกันหลายๆงาน ให้กับหลายๆ core หรือเพื่อประมวลผลงานของเราใน cloud CPU กันเลยดีกว่า แบบนี้

var task1 = ComplexCalculation();
var task2 = ComplexCalculation();
task1.Wait(); task2.Wait();

code ข้างต้นเป็นการสร้าง task เพื่อ run ComplexCalculation 2 งานโดยไม่มีการรอ แต่เราจะจบการรอ 2 งาน สำเร็จด้วยจุดเดียว ซึ่งงานนี้เราจะใช้ method ของ Task class ที่ชื่อว่า WhenAll เพื่อรอคอยผลลัพย์งานของเราทั้งหมดกลับมาพร้อมๆกัน แบบนี้ครับ

await Task.WhenAll(task1, task2);

ก็จบละครับ C# 5: แนะนำให้รู้จัก keyword ใหม่สำหรับ Asynchronous Functions ขอให้สนุกกับซอฟแวร์งานประยุกต์ โดยเขียน code ด้วย C#5 นะครับ

ขอบคุณครับ

#:P

“ใครเก่งกว่าเราเพียงไร อย่าได้แคร์

ใครเกิดมาดีกว่าเราเพียงไหนก็ อย่าได้แคร์

ให้จงมุ่งมั่นตั้งใจ แล้วนำพาใจเราไปให้ถึงฝัน แคร์ได้แค่นี้พอ”

Advertisements

#net, #c