11/18/2019
Lập trình Ứng dụng quản lý
Multithread, Asynchronous Programing
Nội dung
Callback function
Khái niệm MultiThread
Asynchronous Programing
1
11/18/2019
Nội dung
Callback function
Khái niệm MultiThread
Asynchronous Programing
Khái niệm Callback function
Trong lập trình máy tính, callback là một đoạn
code chạy được (thường là một hàm A) được sử
dụng như tham số truyền vào của hàm B nào đó.
Tới một thời điểm nào đó, Hàm A sẽ được hàm B
gọi lại (callback). Các ngơn ngữ lập trình khác
nhau hỗ trợ callback theo các cách khác nhau,
thường được triển khai dưới dạng chương trình
con, hàm nặc danh, chuỗi lệnh hoặc con trỏ hàm.
2
11/18/2019
Callback trong .NET
Trong .NET thì sử dụng delegate để thực hiện
callback.
Ví dụ:
static void FindPrimes(int min, Action<int> callback)
{
for (int i = min; i < int.MaxValue; i++)
{
// kiểm tra i có phải là Prime ko?
callback(i);
}
}
Event bản chất cũng là callback.
Nội dung
Callback function
Khái niệm MultiThread
Asynchronous Programing
3
11/18/2019
Multithreading
Máy tính ngày nay thực hiện được đa nhiệm, đa
tác vụ đồng thời dựa vào khái niệm Process và
Thread.
Process (tiến trình) có thể hiểu là một thể hiện
(instance) của chương trình máy tính được thực
thi, dựa trên hệ điều hành, hồn tồn độc lập với
các tiến trình khác.
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 không được xử lý đúng cách.
Ngữ cảnh sử dụng
Tương tác với giao diện trong khi các tác vụ
ngầm vẫn chạy
Lướt web trong khi Chrome đồng bộ bookmark
Thiết lập độ ưu tiên
Hoạt động tiêu tốn nhiều thời gian khơng dừng
tồn bộ ứng dụng
Copy file dung lượng lớn!
Import file vào CSDL.
…
4
11/18/2019
Namespace System.Threading
Cung cấp lớp và giao diện cho lập trình đa luồng
Class chính: Thread
CurrentThread
IsAlive
IsBackground
Name
Priority
ThreadState
Tạo một Thread
Tạo phương thức (gọi là phương thức callback)
sẽ thực thi khi thread được gọi.
Phương thức này phải khơng có tham số hoặc chỉ
có một tham số là kiểu object và kiểu trả về là void.
Bước này có thể bỏ qua vì ta có thể sử dụng
anonymous method hoặc lambda expression để tạo
đoạn mã lệnh thực thi in-line cùng với lệnh khởi tạo
thread.
Tạo đối tượng Thread và truyền một delegate
ThreadStart chứa phương thức sẽ thực thi vào
constructor của Thread.
Chạy thread: Gọi phương thức Start() của đối
tượng thread vừa tạo.
5
11/18/2019
Ví dụ
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(MethodA));
Thread t2 = new Thread(new ThreadStart(MethodB));
t1.Start();
t2.Start();
}
static void MethodA()
{
for (int i = 0; i < 100; ++i)
Console.Write("A");
}
static void MethodB()
{
for (int i = 0; i < 100; ++i)
Console.Write("B");
}
Sử dụng lambda expression
static void Main(string[] args)
{
Thread t1 = new Thread(() => {
for (int i = 0; i < 100; ++i)
Console.Write("A");
});
Thread t2 = new Thread(() => {
for (int i = 0; i < 100; ++i)
Console.Write("B");
});
t1.Start();
t2.Start();
}
6
11/18/2019
Truyền tham số cho Thread
ParameteriedThreadStart là một giải pháp thay
thế cho ThreadStart trong trường hợp cầ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 cần phải ép kiểu để
sử dụng được đúng kiểu dữ liệu của tham số.
Ví dụ
static void Main(string[] args)
{
Thread t1 = new Thread(new ParameterizedThreadStart(Method));
Thread t2 = new Thread(new ParameterizedThreadStart(Method));
t1.Start("A");
t2.Start("B");
}
static void Method(object param)
{
string str = (string)param;
for (int i = 0; i < 100; ++i)
Console.Write(str);
}
7
11/18/2019
Sử dụng lambda expression
static void Main(string[] args)
{
Thread t1 = new Thread((param) => {
string str = (string)param;
for (int i = 0; i < 100; ++i)
Console.Write(str);
});
Thread t2 = new Thread((param) =>
{
string str = (string)param;
for (int i = 0; i < 100; ++i)
Console.Write(str);
});
t1.Start("A");
t2.Start("B");
}
Property quan trọng của Thread
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 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.
8
11/18/2019
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 ta
nên sử dụng những phương pháp khác để thay
thế.
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 do đó
khơng cần tạo đối tượng Thread khi gọi nó, ví dụ:
Thread.Sleep(1000). Tùy vào ngữ cảnh gọi
Thread.Sleep(), mà Thread thực thi dòng lệnh
này sẽ bị ảnh hưởng.
Join(): đây là một phương thức hữu ích trong
trường hợp cầ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 đã 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 đó hồn tất cơng
việc của mình.
9
11/18/2019
Ví dụ
static void Main(string[] args)
{
Thread t1 = new Thread(new ParameterizedThreadStart(Method));
Thread t2 = new Thread(new ParameterizedThreadStart(Method));
Thread t3 = new Thread(new ParameterizedThreadStart(Method));
t1.Start(new object[] { "A", 0 });
t2.Start(new object[] { "B", 100 });
t2.Join();
t3.Start(new object[] { "C", 0 });
}
static void Method(object param)
{
object[] arrObj = (object[])param;
string str = (string)arrObj[0];
int n = (int)arrObj[1];
Thread.Sleep(n);
for (int i = 0; i < 100; ++i)
Console.Write(str);
}
Kết quả là C chỉ xuất hiện sau B dù có làm chậm Thread 2 đi.
Foreground và Background Thread
Foreground Thread là những Thread sẽ được
thực thi cho đến khi kết thúc mặc dù chương
trình (Thread chính) đã hồn thành (hoặc bị bắt
buộc ngừng) và kết thúc.
Background Thread thì sẽ bị khai tử ngay khi
chương trình kết thúc.
Mặc định các Thread khi tạo ra là Foreground.
Thuộc tính để set Foreground hay Background là
IsBackground.
10
11/18/2019
Ví dụ
static void Main(string[] args)
{
Thread t1 = new Thread(() => {
Thread.Sleep(1000);
Console.WriteLine("Thread 1 running...");
});
Thread t2 = new Thread(() => {
Thread.Sleep(1200);
Console.WriteLine("Thread 2 running...");
});
t1.Start();
t2.IsBackground = true;
t2.Start();
Console.WriteLine("Main ended...");
}
Thread Pooling
Thread Pooling là một kĩ thuật cho phép sử
dụng các thread hiệu quả hơn bằng cách quản lý
và phân phối chúng hợp lý, tận dụng tối đa thời
gian nhàn rỗi và tăng hiệu suất của chương trình.
Thread Pooling là một kĩ thuật được áp dụng
phổ biến trong các ứng dụng về I/O bất đồng bộ
tập tin và truyền tải dữ liệu trên mạng.
Một đặc điểm của Thread pool là các thread sẽ
được đặt ở chế độ background (Background
Thread).
Để sử dụng thread pool thì chỉ đơn giản sử dụng
phương thức tĩnh QueueUserWorkItem() của lớp
ThreadPool.
11
11/18/2019
Ví dụ
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(funcWait, "A");
ThreadPool.QueueUserWorkItem(funcWait, "B");
Thread.Sleep(6000);
}
static void funcWait(object param)
{
string str = (string) param;
for (int i = 0; i < 10; ++i)
{
Console.WriteLine("{0} running...", str);
Thread.Sleep(500);
}
}
Các vấn đề về đụng độ
Vấn đề nảy sinh khi các Thread cùng truy cập đến một đối tượng, trạng thái chia
sẻ bất đồng bộ.
Ví dụ chương trình sau
static int n = 0;
static void Main(string[] args)
{
Thread t1 = new Thread(() => {
for (int i = 0; i < 100; ++i){
n++;
if (n > 0){
Thread.Sleep(2); Console.Write("{0}\t", n);
}
}
});
Thread t2 = new Thread(() => {
for (int i = 0; i < 100; ++i){
Thread.Sleep(1); n--;
}
});
t1.Start(); t2.Start();
}
12
11/18/2019
Giải quyết vấn đề bằng đồng bộ
.Net cung cấp một số kĩ thuật để đồng bộ việc
truy xuất dữ liệu. Một khi được sử dụng, dữ liệu
sẽ bị khóa lại và các thread khác muốn sử dụng
phải chờ cho đến khi dữ liệu hay tài nguyên được
giải phóng.
Cụ thể các phương pháp được cung cấp là:
Monitor, SpinLock, Mutex (Mutual exclusive),
Semaphore, WaitHandle,...
Đơn giản nhất là sử dụng Lock.
Ví dụ
static int n = 0;
static object syncObj = new object();
static void Main(string[] args)
{
Thread t1 = new Thread(() => {
for (int i = 0; i < 100; ++i){
lock (syncObj){
n++;
if (n > 0){
Thread.Sleep(2); Console.Write("{0}\t", n);
}
}
}
});
Thread t2 = new Thread(() => {
for (int i = 0; i < 100; ++i){
lock (syncObj){
Thread.Sleep(1); n--;
}
}
});
t1.Start(); t2.Start();
}
13
11/18/2019
Deadlock
Đồng bộ hóa khi sử dụng thread là một cơng việc
cần thiết, tuy nhiên nếu khơng cẩn thận thì sẽ
gặp phải tình trạng chương trình dừng hoạt động
vơ thời hạn. Tình trạng này được đặt tên là
Deadlock.
Deadlock xảy ra khi có ít nhất hai thread cùng
đợi thread kia giải phóng, thật “trùng hợp” là cả
hai lại đang giữ “chìa khóa” của nhau.
Ví dụ
static int m = 0, n = 0;
static object syncM = new object();
static object syncN = new object();
static void Main(string[] args)
{
Thread t1 = new Thread(() => {
lock (syncM){
m++; Thread.Sleep(100); Console.WriteLine("t1 holding m");
lock(syncN){
n--; Console.WriteLine("t1 holding n");
}
}
});
Thread t2 = new Thread(() => {
lock (syncN)
{
n++; Thread.Sleep(100); Console.WriteLine("t2 holding n");
lock (syncM)
{
m--; Console.WriteLine("t2 holding m");
}
}
});
t1.Start(); t2.Start();
}
14
11/18/2019
Nội dung
Callback function
Khái niệm MultiThread
Asynchronous Programing
Asynchronous Programing
Là cơ chế xử lý đồng thời nhiều request bằng các
thread khác nhau trong khi đợi kết quả từ các tác
vụ tiêu tốn tài nguyên lớn như I/O, access
database,…
Lợi ích đạt được là:
Trải nghiệm người dùng (usability).
Hiệu năng (performance).
15
11/18/2019
BeginInvoke và EndInvoke
Delegate .NET có 2 phương thức giúp thực hiện
Asynchronous Programing là:
BeginInvoke (args, AsyncCallback, @object) trả về
IAsyncResult: tự động tạo thread mới và thực hiện
tác vụ trong thread mới này.
EndInvoke (IAsyncResult) trả về kết quả của tác vụ
thực hiện trong delegate.
Task, Async, Await
Từ .NET 4.5 đưa ra framework mới để thực hiện
Asynchronous Programing.
Một phương thức để thực thi asynchronous thì
cần sử dụng kiểu trả về là Task.
Ví dụ
Task<int> taskGetNumber()
{
return Task<int>.Run(() => {
// tác vụ tiêu tốn nhiều thời gian
return 1;
});
}
async void DoIt()
{
int num = await taskGetNumber();
// thực hiện các việc khác trong khi chờ kết quả
}
16