[VB.NET] เขียนคำสั่งแบบ Asynchronous ด้วย BackgroundWorker

สำหรับการเขียนโปรแกรมแบบ Asynchronous บน VB.NET นั้น เป็นเรื่องที่ยาก เนื่องจากต้องเขียนคำสั่งแยก Thread ซึ่งมันทั้งยุ่งและยาก ในด้านการคิด และการทำ

Visual Studio 2013 Logo

บน VB.NET นั้น มี Control ที่ชื่อว่า BackgroundWorker ซึ่งมาช่วยจัดการเรื่อง Thread ให้เป็นเรื่องง่าย (รึเปล่า) มาดูวิธีใช้กันเลย

 

List of contents

ก่อนอื่น มาทำความรู้จักกับคำว่า Synchronous และ Asynchronous กันก่อน

 

Synchronous vs Asynchronous

การเขียนคำสั่งแบบ Synchronous ก็คือการเขียนคำสั่ง ที่ชุดคำสั่งนั้น ทำงานเรียงตามลำดับบรรทัดไปนั่นเอง

ลองดูรูปต่อไปนี้ UI Thread คือ Thread หลักที่เรามักจะเขียนคำสั่งจัดการเกี่ยวกับหน้าจอโปรแกรม และอื่น ๆ แทนด้วยเส้นสีเทา ส่วนเส้นสีเขียว คือชุดคำสั่งชุดหนึ่ง ที่ทำงานตั้งแต่ต้นจนจบ ตามเส้นเวลา ซึ่งก็จะแทรกอยู่บน UI Thread

การทำงานแบบ Synchronous
การทำงานแบบ Synchronous

ส่วนการเขียนคำสั่งแบบ Asynchronous ก็คือการเขียนคำสั่ง ที่แยกการทำงานออกไปอีก Thread นึง

ลองดูรูปต่อไปนี้ เส้นสีส้ม คือชุดคำสั่งชุดหนึ่ง ที่แยกออกไปทำงานอีก Thread ไม่ยุ่งกับ UI Thread ส่วนของ UI Thread ก็สามารถทำชุดคำสั่งอื่น ๆ ต่อไปได้

การทำงานแบบ Asynchronous
การทำงานแบบ Asynchronous

เพื่อให้เห็นภาพ ลองดู Video ต่อไปนี้ เป็นการเขียนโปรแกรมจำลองการ Download ซึ่งถ้าเรานำชุดคำสั่งสำหรับ Download ไปไว้บน UI Thread จะเกิดเหตุการณ์แบบนี้

 

สังเกตุว่า เมื่อกดปุ่ม Start เพื่อจำลองการ Download ทั้งหน้าจอเหมือนจะถูกแช่แข็งไปหมด Ugh! (ยกเว้น Progressbar ซึ่งมันสามารถ Refresh ได้อัตโนมัติ)

แต่หากว่าเรานำคำสั่ง Download แยกไปไว้อีก Thread ก็จะสามารถทำงานได้แบบ Video ต่อไปนี้

 

ทั้งหน้าจอ ไม่ถูกแช่แข็งแล้ว Ha Ha

โปรแกรมดูดีขึ้นกว่าเยอะเลยใช่ไม๊ครับ

 

BackgroundWorker Control

อย่างที่เกริ่นไปตอนต้น การเขียนโปรแกรมแยก Thread นั้นยุ่งยาก แต่ BackgroundWorker Control มาทำให้มันง่าย โดย BackgroundWorker นั้น มีให้ใช้ตั้งแต่ .NET Framework 2.0 นั่นแปลว่า เราจะใช้ Visual Studio.NET version 2005 ขึ้นไป ก็สามารถใช้งาน BackgroundWorker ได้แล้ว โดย BackgroundWorker Control นั้น อยู่ใน Toolbox ในกลุ่ม Components ซึ่งจะเป็น Control ที่ไม่มี UI (เหมือน Timer Control นั่นแหล่ะ)

สำหรับ BackgroundWorker Control นั้น มีส่วนประกอบที่ควรรู้ดังนี้ (สามารถดูรายละเอียดทั้งหมดได้จาก BackgroundWorker Class)

  • Property
    • CancellationPending – เป็นชนิด Boolean จะมีค่าเป็น True เมื่อมีการเรียกใช้ Method CancelAsync
    • IsBusy – เป็นชนิด Boolean จะมีค่าเป็น True เมื่อ BackgroundWorker ทำงานอยู่
    • WorkerReportsProgress – เป็นชนิด Boolean สำหรับกำหนดว่า จะให้มีการแจ้งความคืบหน้าของงานที่กำลังทำอยู่หรือไม่ โดยจะมี Default เป็น False หากกำหนดเป็น True สามารถแจ้งความคืบหน้าได้ด้วย Method ReportProgress และสามารถดัก Event ได้จาก Event ProgressChanged (สามารถกำหนดค่าได้ตั้งแต่ Design)
    • WorkerSupportsCancellation – เป็นชนิด Boolean สำหรับกำหนดว่า จะให้มีการยกเลิกงานที่ทำค้างอยู่ได้หรือไม่ โดยจะมี Default เป็น False หากกำหนดเป็น True สามารถใช้งาน Method CancelAsync ได้ และจะแจ้งว่ามีการ Cancel ผ่านทาง Parameter ของ Event RunWorkerCompleted (สามารถกำหนดค่าได้ตั้งแต่ Design)
  • Method
    • CancelAsync – เป็น Method ที่ไม่มี Parameter และไม่มีการ Return ค่า ใช้สำหรับแจ้งให้ BackgroundWorker หยุดทำงานกลางคัน
    • RunWorkerAsync – เป็น Method ที่มี 2 Overload (ไม่มี Parameter และมี 1 Parameter) และไม่มีการ Return ค่า ใช้สำหรับเริ่มต้นการทำงานใน Thread ใหม่
    • ReportProgress – เป็น Method ที่มี 1 Parameter และไม่มีการ Return ค่า ใช้สำหรับแจ้งความคืบหน้าของงานที่ทำอยู่
  • Event
    • DoWork – เป็น Event ที่ทำงานเมื่อเรียกใช้งาน Method RunWorkerAsync ซึ่ง Code ที่อยู่ภายใน Event นี้ จะทำงานใน Thread ใหม่
    • ProgressChanged – เป็น Event ที่ทำงานเมื่อเรียกใช้งาน Method ReportProgress ซึ่ง Code ที่อยู่ภายใน Event นี้ จะทำงานบน UI Thread
    • RunWorkerCompleted – เป็น Event ที่ทำงานเมื่อ Thread ใหม่ทำงานเสร็จ ไม่ว่าจะเสร็จสมบูรณ์ จะถูก Cancel หรือจะเกิด Error ก็จะเกิด Event นี้ เป็น Event สุดท้าย

เพื่อให้เห็นภาพมากขึ้น ลองดูภาพต่อไปนี้

Flow การทำงานของ BackgroundWorker
Flow การทำงานของ BackgroundWorker

บน UI Thread เราจะเรียกใช้ Method RunWorkAsync ในการเริ่มทำงานใน Thread ใหม่ (เส้นสีส้ม) ซึ่งจะต้องเขียน Code ใน Event DoWork

ระหว่างที่ Event DoWork ทำงาน สามารถใช้ Method ReportProgress เพื่อแจ้งให้ UI Thread ทราบความคืบหน้า ทาง Event ProgressChanged

เมื่อ Thread ใหม่ทำงานเสร็จแล้ว จะแจ้งให้ UI Thread ทราบ ทาง Event RunWorkerCompleted

ส่วนเส้นประสีแดง คือ สามารถยกเลิกงานกลางคัน โดยเรียกใช้ Method CancelAsync มาจากฝั่งของ UI Thread

และมีอีกสิ่งที่ผู้ที่ไม่เคยเขียนคำสั่งนอก UI Thread ควรรู้ นั่นคือ

บน Thread อื่น ๆ ที่แยกออกมาจาก UI Thread ไม่สามารถเข้าถึง Resource ของ UI Thread ได้

ยกตัวอย่างง่าย ๆ 2 จุด ก็คือ

  1. เราไม่สามารถอ่านค่าใด ๆ ที่เป็นของ UI Thread ได้ เช่น Property ของ Control แต่ถ้าจำเป็นต้องทำ สามารถส่งข้อมูลจาก UI Thread ให้ Thread ใหม่ได้ ด้วย Parameter ใน Method RunWorkerAsync
  2. เราไม่สามารถกำหนดค่าใด ๆ ที่เป็นของ UI Thread เช่น Property ของ Control แต่ถ้าจำเป็นต้องทำ สามารถส่งข้อมูลจาก Thread ใหม่ ให้ UI Thread ได้ ด้วย Method ReportProgress

หรือสรุปได้ว่า UI Thread และ Thread ใหม่ ไม่สามารถสื่อสารกันได้โดยตรง แต่ใช้ความสามารถของ BackgroundWorker ที่มีอยู่ ช่วยได้

ลองย้อนขึ้นไปดูรูปด้านบนอีกครั้ง น่าจะเข้าใจได้มากขึ้น

 

Design Interface

ตอนนี้ขอให้ทุกคน เปิด Visual Studio.NET ขึ้นมา สร้าง Project ใหม่ เป็นชนิด Windows Forms Application แล้วออกแบบหน้าจอ ให้เหมือนใน Video ตัวอย่างด้านบน (และเพิ่ม Timer Control ไป 1 ตัว และ BackgroundWorker Control ไป 1 ตัว)

เพื่อความรวดเร็ว สามารถลอก Code ส่วน Design ได้จาก frmBGWorker.Designer.vb หรือ Code ด้านล่างนี้

 

Simulate Download on UI Thread

เขียนคำสั่งตาม Code ที่ frmBGWorker1.vb

อธิบาย Code คร่าว ๆ ดังนี้

  • บรรทัดที่ 5 – ประกาศตัวแปร 1 ตัว เพื่อเก็บสถานะ ว่ากำลัง Download อยู่หรือไม่
  • บรรทัดที่ 9-17 – ส่วนของ Constructor สำหรับกำหนดค่าเริ่มต้นต่าง ๆ
  • บรรทัดที่ 21-24 – Function สำหรับแสดงผลความคืบหน้าของการ Download
  • บรรทัดที่ 26-36 – Function สำหรับจำลองการ Download
  • บรรทัดที่ 38-40 – Function สำหรับยกเลิกการ Download
  • บรรทัดที่ 44-53 – Event Click ของปุ่ม Start เพื่อทำการจำลองการ Download
  • บรรทัดที่ 55-61 – Event Click ของปุ่ม Stop เพื่อทำการยกเลิกการ Download
  • บรรทัดที่ 63-65 – Event Tick ของ Timer เพื่อใช้แสดงเวลา (ให้เห็นว่า UI ยังมีการ Update อยู่เสมอ) และแสดงค่าตัวแปรสถานะ ว่ากำลัง Download อยู่หรือไม่

ลองรันโปรแกรมดู จะเห็นว่า ทำงานเหมือน Video ที่ 1

 

Simulate Download with BackgroundWorker

เมื่อเราเขียนใหม่ โดยนำ BackgroundWorker Control มาใช้ สามารถเขียนคำสั่งได้ตาม Code ที่ frmBGWorker2.vb

อธิบาย Code คร่าว ๆ ดังนี้ (ขออธิบายเฉพาะส่วนที่แตกต่างจากตัวอย่างก่อนหน้า)

  • บรรทัดที่ 29 – เรียกใช้งาน Method RunWorkerAsync แบบไม่มี Parameter ใช้ในกรณีไม่มีการส่งข้อมูลให้ Thread ใหม่
  • บรรทัดที่ 32-33 – เรียกใช้งาน Method RunWorkerAsync แบบมี Parameter ซึ่ง Parameter เป็นชนิด Object แปลว่า เราสามารถใส่อะไรลงไปก็ได้
  • บรรทัดที่ 37 – เรียกใช้งาน Method CancelAsync เพื่อยกเลิกการทำงานของ Thread ใหม่กลางคัน
  • บรรทัดที่ 41 – ใช้ Property IsBusy ของ BackgroundWorker ในการตรวจสอบการทำงาน
  • บรรทัดที่ 46 – ตรวจสอบ Argument ว่ามีการส่งค่ามาจาก UI Thread (จาก Method RunWorkerAsync) หรือไม่ ถ้ามี ก็ทำการ Casting ไปเป็นชนิดข้อมูลที่ตรงกับชนิดข้อมูลที่ส่งมา (e.Argument เป็นชนิด Object จึงควรจะทำการ Casting ด้วย)
  • บรรทัดที่ 49 – ตรวจสอบ Property CancellationPending ว่ามีการ Cancel งาน มาจาก Method CancelAsync หรือไม่ ถ้ามี ให้กำหนดค่าตัวแปร e.Cancel (ชนิด Boolean) ให้เป็น True เพื่อยืนยันการยกเลิก ซึ่งเราสามารถทำ Statement ที่จำเป็นต้องทำให้จบก่อน ค่อยสั่งหยุดทำงานได้
  • บรรทัดที่ 51 – เรียกใช้งาน Method ReportProgress เพื่อแจ้งความคืบหน้าของงาน โดยจะส่งค่าความคืบหน้าออกไปทาง Parameter ซึ่งเป็นชนิด Integer
  • บรรทัดที่ 57 – กรณีที่ต้องการส่งข้อมูลบางอย่างจาก Thread ไปยัง UI Thread เมื่อทำงานเสร็จแล้ว สามารถกำหนดได้ผ่านทางตัวแปร e.Result ซึ่งเป็นชนิด Object
  • บรรทัดที่ 60-62 – เป็นการดัก Event ProgressChanged ซึ่งจะเกิดเมื่อมีการเรียกใช้ Method ReportProgress และสามารถอ่านค่าความคืบหน้าได้จากตัวแปร e.ProgressPercentage ซึ่งเป็นชนิด Integer
  • บรรทัดที่ 64-76 – เป็นการดัก Event RunWorkerCompleted ซึ่งจะเกิดเมื่อการทำงานเสร็จสมบูรณ์, ถูก Cancel หรือจะเกิด Error ก็ตาม ซึ่งสามารถตรวจสอบกรณีต่าง ๆ ได้จากตัวแปร e

ลองรันโปรแกรมดู จะเห็นว่า ทำงานเหมือน Video ที่ 2

 

เพียงเท่านี้ เราก็สามารถเขียนโปรแกรมแบบ Asynchronous บน VB.NET ได้แล้ว ไม่ยากเกินไปใช่ไม๊ครับ

ใครบ่นว่ายาก เดี๋ยวจะไล่ให้ไปใช้ Class Thread นะเอ้อ Hell Boy

Leave a Reply

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.