Tuesday, July 15, 2014

Bài 13 : Cơ bản về Thread - Lập trình đa luồng

[Bài 13 : Cơ bản về Thread - Lập trình đa luồng]
Thread hay còn gọi là tiểu trình là khái niệm khá quen thuộc trong lập trình. Thread cho phép chương trình thực hiện đồng thời nhiều tác vụ, và giúp quá trình tương tác với người dùng không bị gián đoạn, lập trình song song và là kĩ thuật không thể thiếu trong các ứng dụng về mạng. Trong bài này, bạn sẽ được giới thiệu cơ bản về cách làm việc với thread cũng như kĩ thuật đồng bộ hóa và hiện tượng deadlock.
Bạn không nên nhầm lẫn giữa process (tiến trình) và thread (tiểu trình). Process có thể hiểu là một instance của chương trình máy tính được thực thi, dựa trên hệ điều hành, hoàn toàn độc lập với các tiến trình khác. Còn thread là một nhóm lệnh được tạo ra để thực thi một tác vụ trong một process, chúng chia sẻ chung dữ liệu với nhau để xử lý, điều này là cần thiết nhưng cũng là nguyên nhân dễ gây ra lỗi nếu bạn không xử lý đúng cách.
 Tạo và thực thi thread
class Program
{
static void Main()
{
Thread t = new Thread(new ThreadStart(MethodA));
t.Start();
MethodB();
}
static void MethodA()
{
for (int i = 0; i < 100; i++)
Console.Write("0");
}
static void MethodB()
{
for (int i = 0; i < 100; i++)
Console.Write("1");
}
}
Nhấn Ctrl+F5 để chạy (không dùng debug), ta sẽ có kết quả sau
Output:
00000000000000111111111111111111111111111111111111111111111111111110000000000000
00000000000000000000000000000000000000001111111111111111111111111111111111111111
1111111000000000000000000000000000000000
Press any key to continue . . .
Nếu bạn không dùng thread chạy lần lượt 2 phương thức MethodA() và MethodB() thì kết quả in ra sẽ là 100 kí tự ‘1’ và sau đó là 100 kí tự ‘0’. Tuy nhiên như bạn thấy khi dùng thread như ví dụ trên, kết quả in ra sẽ là một chuỗi lẫn lộn ‘1’ và ‘0’, tức là hai phương thức này chạy đồng thời với nhau.
Một cách ngắn gọn hơn, thay vì truyền một đối tượng ThreadStart bạn có thể truyền trực tiếp tên phương thức cần thực thi cho constructor của Thread. Trình biên dịch sẽ tự động tạo ra đối tượng ThreadStart dựa vào phương thức mà bạn truyền vào:
Thread t = new Thread(MethodA);
 Lambda expression là một phương pháp hữu ích để viết trực tiếp mã lệnh cần thực thi mà không phải tách riêng ra thành phương thức:
Thread t = new Thread(()=>
{
Console.Write(“Hello”);
});
 Truyền tham số cho Thread
ParameteriedThreadStart là một giải pháp thay thế cho ThreadStart trong trường hợp bạn muốn truyền tham số cho thread. Đối tượng delegate ParameteriedThreadStart này chỉ chấp nhận một tham số kiểu object, vì thế trong phương thức callback, bạn cần phải ép kiểu để sử dụng được đúng kiểu dữ liệu của tham số.
namespace ThreadExample
{
class Student
{
public string Name { get; set; }
public DateTime BirthDay { get; set; }
}
class Program
{
static void Main()
{
Thread t1 = new Thread(Print);
t1.Start(new Student() { Name = "Yin", BirthDay = new DateTime(1989, 10, 17) });
Console.ReadKey();
}
static void Print(object obj)
{
Student st = (Student)obj;
Console.Write(st.Name + "\t" + st.BirthDay.ToShortDateString());
}
}
}
Output:
Yin 17/10/1989
 Phiên bản sử dụng lambda expression với ví dụ trên:
namespace ThreadExample
{
class Student
{
public string Name { get; set; }
public DateTime BirthDay { get; set; }
}
class Program
{
static void Main()
{
Thread t1 = new Thread((obj) =>
{
Student st = (Student)obj;
Console.Write(st.Name + "\t" + st.BirthDay.ToShortDateString());
});
t1.Start(new Student() { Name = "Yin", BirthDay = new DateTime(1989, 10, 17) });
Console.ReadKey();
}
}
}
 Thuộc tính ThreadState và ThreadPriority
ThreadState
Thuộc tính ThreadState cho thấy trạng thái hiện tại của thread. Mỗi một lời gọi phương thức của thread sẽ làm thay đổi giá trị thuộc tính này như Unstarted, Running, Suspended, Stopped, Aborted,….
ThreadPriority
Thuộc tính này xác định mức độ ưu tiên mà thread sẽ được thực thi so với các thread khác. Mỗi thread khi được tạo ra mang giá trị priority là Normal. Các giá trị mà thuộc tính có thể có bao gồm: Lowest, BelowNormal, Normal, AboveNormal và Highest.
 Các phương thức thông dụng của Thread
- Abort(): khi phương thức này được gọi, hệ thống sẽ ném ra một ngoại lệThreadAbortException để kết thúc thread. Sau khi gọi phương thức này, thuộc tính ThreadState sẽ chuyển sang giá trị Stopped.
- Suspend(): phương thức này sẽ tạm dừng việc thực thi của Thread vô thời hạn cho đến khi nó được yêu cầu chạy tiếp tục với phương thức Resume(). Tuy nhiên hai phương thức này được gắn attribute Obsolete để khuyến cáo rằng bạn nên sử dụng những phương pháp khác để thay thế. Các kĩ thuật này sẽ được giới thiệu trong một bài khác.
- Sleep(): để dừng thread hiện tại trong một khoảng thời gian tính bằng milisecond, khi đó thread sẽ chuyển sang trạng thái WaitSleepJoin. Chú ý rằng đây là một phương thức static và bạn không cần tạo đối tượng Thread khi gọi nó, ví dụ: Thread.Sleep(1000). Tôi nhấn mạnh chữ hiện tại tức là tùy vào vị trí mà bạn gọi Thread.Sleep(), mà Thread thực thi dòng lệnh này sẽ bị ảnh hưởng. Nếu như bạn không tạo thêm Thread thì Thread đang thực thi chương trình sẽ bị ảnh hưởng (chương trình sẽ tạm ngừng hoạt động).
Một ví dụ đơn giản để bạn có thể thấy ảnh hưởng của Sleep():
class Program
{
static void Main()
{
Thread t = new Thread(MethodA);
t.Start();
MethodB();
}
static void MethodA()
{
Thread.Sleep(500); // sleep for 500 miliseconds
for (int i = 0; i < 100; i++)
Console.Write("0");
}
static void MethodB()
{
for (int i = 0; i < 100; i++)
Console.Write("1");
}
}
Ouput:
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000
- Join(): đây là một phương thức hữu ích trong trường hợp bạn muốn thực hiện một tác vụ nào đó sau khi thread đã kết thúc. Phương thức này chỉ được dùng sau khi bạn đã chạy Thread. Các tác vụ nằm phía dưới lệnh gọi Join() của một Thread chỉ được thực thi sau khi Thread đó hoàn tất công việc của mình.
Hãy xem ví dụ sau:
class Program
{
static void Main()
{
Thread t1 = new Thread(MethodA);
Thread t2 = new Thread(MethodB);
Thread t3 = new Thread(MethodC);
t1.Start();
t2.Start();
t2.Join();
t3.Start();
}
static void MethodA()
{
for (int i = 0; i < 100; i++) Console.Write("0");
}
static void MethodB()
{
for (int i = 0; i < 100; i++) Console.Write("1");
}
static void MethodC()
{
for (int i = 0; i < 100; i++) Console.Write("2");
}
}
Output:
00000000000000000000000000000000111111111111111111111111111111111111111111111111
11100000000000000000000000000000000000000000000000000000111111111111111111111111
11111111111111111111111110000000000000002222222222222222222222222222222222222222
222222222222222222222222222222222222222222222222222222222222

0 nhận xét:

Post a Comment