ภาคนี้ไม่มีอะไรมากครับ คือว่า ผมได้แนวดีๆ มาอีกแนวคือ มันจะมีอีกกรณีที่เราแค่ต้องการจะทำอะไรที่มันนานๆ แต่ว่าโปรแกรมเราก้อต้องหยุดรออยู่ดี แล้วเราก้อบอกไม่ได้เป็น Progress Bar เช่น ส่งข้อมูลไป Web Service
ผมก้อเลยทดลองเล่นๆ และก็นำเอาเทคนิคของ DoEvents และ Anonymous Method ผสม Thread มายำรวมกัน ได้แบบนี้ครับ
public delegate void Work();
/// <summary>
/// Utility function to run the specified work without blocking the UI thread
/// from processing event. This is not multi-threaded solution and should be
/// used only for running task 'synchronously'. UI will have a chance to
/// process event every 50 ms.
/// </summary>
/// <param name="toRun"></param>
protected Exception RunTaskWithoutBlocking(Work toRun)
{
Exception exception = null;
ManualResetEvent workComplete = new ManualResetEvent(false);
Work work = delegate
{
try
{
toRun();
}
catch (Exception ex)
{
exception = ex;
}
finally
{
workComplete.Set();
}
};
work.BeginInvoke(null, null);
while ( !workComplete.WaitOne( 50, true ))
{
System.Windows.Forms.Application.DoEvents();
}
return exception;
}
ส่วนการใช้งาน ก็ใช้แบบนี้ครับ
Exception ex = this.RunTaskWithoutBlocking(delegate
{
DoSomething();
});
if (ex != null)
{
Handle(ex);
}
จริงๆ ผมก็ไม่มีอะไรอธิบายมากครับ โค๊ดค่อนข้างตรงๆ เลยทีเดียว นั่นก็คือ ผมให้ work ซึ่งเป็น Anonymous Method ครอบงานที่ต้องการจะทำงานไว้ แล้วเรียกให้ work ทำงานแบบ Asynchronous (หรือว่าเปิด Thread ใหม่นั่นแหละ) โดยใช้ฟังก์ชั่น BeginInvoke ซึ่งผู้ที่คุ้นเคยกับ BeginInvoke อยู่แล้ว อาจจะสงสัยว่า ทำไมผมจึงไม่ใช้แบบโค๊ดด้านล่างนี้่เลย ทำไมต้องไปสร้าง Anonymous Method มาครอบอีก
toRun.BeginInvoke(delegate
{
workComplete.Set();
}, null);
นั่นก็เพราะว่า ผมต้องการจะจับ Exception กลับไปด้วยครับ ไม่อย่างนั้นแล้ว ตอนเกิด Exception มันจะไปเกิดตรงด้านใน toRun ซึ่งจะทำให้ไม่มีใครสามารถดักไว้ได้เลย ผมจึงต้องทำการ Try Catch ภายในตัว Anonymous Method อีกครั้งหนึ่ง เพื่อจะเก็บ Exception เอาไว้ให้ได้
แล้วจากนั้น ผมก็ใช้ ManualResetEvent ซึ่งเป็นคลาสด้าน Synchronization อย่างนึง มาหยุด (Block) ตัว ฟังก์ชั่น RunTaskWithoutBlocking ไว้ แต่ว่า เนื่องจาก ManualResetEvent.Waitone เนี่ย ผมสามารถใส่ Timeout ให้มันได้ ดังนั้น ถ้ามันรอไป 50 ms แล้ว มันยังไม่เสร็จซะที มันก้อจะหลุดเข้ามาใน While Loop ซึ่งผมจะสั่ง DoEvents เพื่อให้ UI เกิดการอัพเดท หรือว่ารับ Input ได้ (กรุณากลับไปอ่านภาค 1 ก่อนนะครับ เรื่องที่ว่าทำไม ไม่ควรใช้ DoEvents หรือกรณีไหน ที่ใช้ไม่ได้)
ซึ่งในการใช้งานจริง ผมแนะนำว่า ฟังก์ชั่นนี้ ควรจะใช้ตอนทีผู้ใช้ ไม่สามารถทำงานอย่างอื่นได้แล้ว นอกจากรอ การใช้ฟังก์ชั่นนี้ ก็จะช่วยให้เขารอแบบมีความหวังหน่อย ไม่ใช้ว่า โปรแกรมค้างไปเฉยๆ เราอาจจะใช้การแสดง Animation นิดหน่อย หรือทำ Progress Bar วิ่งไปเรื่อยๆ โดยใช้ Timer อะไรแบบนี้ได้ครับ ถ้าจะให้ทำอย่างอื่นไปด้วย ไปเปิด Thread ใหม่ไปเลย (ไม่ใช่การใช้ฟังก์ชั่นนี้) จะดีกว่าครับ
ถ้ามีอะไรดีๆ ผมจะเอามาฝากอีกครับ แล้วเจอกันครับ