Trong .NET Framework, Generic được giới thiệu là khái niệm về các kiểu tham số và được dùng để thiết kế class và phương thức nhằm để trì hoãn chỉ ra kiểu dữ liệu cho đến khi lớp hoặc p
Trang 1Multithread và Parallel programming
trong NET
Lập trình Windows
Trang 3Generic Types
Generic nghĩa là “tổng quát” hàm ý đưa ra những cách làm chung nhất cho nhiều vấn đề
Tình huống ví dụ cần xây dựng 1 Ngăn xếp (Stack) chứa kiểu
dữ liệu int với các phương thức cơ bản như: push, pop, kiểm tra,… Sau đó lại cần xây dựng cũng Stack với các phương thức như vậy nhưng dữ liệu lại là string
Giải pháp cho vấn đề trên?
public class IntStack
Trang 4 Cách đầu tiên thường làm: copy lại và thay đổi kiểu
dữ liệu trong code Sẽ tiếp tục như vậy nếu có yêu cầu đổi kiểu dữ liệu sang 1 kiểu dữ liệu khác nữa
như float, double,…
Cách tốt hơn khi đã biết đến kiểu dữ liệu cơ sở trong NET là object Khi sử dụng kiểu dữ liệu object thì
Stack đã chấp nhận được mọi kiểu dữ liệu?
public class ObjectStack
{ object [] _data = new object [100];
Trang 5 Vấn đề của ObjectStack kể trên là dễ gây ra lỗi runtime (tức là chỉ giải quyết tốt về mặt syntax)
ObjectStack s = new ObjectStack (); //push vào kiểu string
s Push ( "test" );
//lấy ra lại kiểu int
int i = ( int ) s Pop ();
NET cung cấp giải pháp cho vấn đề này thông qua khái niệm Generic
Trang 6 Generic là 1 tính năng mới trong NET Framework 2.0 và và được tích hợp sâu trong Common
Language Runtime (CLR)
Trong NET Framework, Generic được giới thiệu là khái niệm về các kiểu tham số và được dùng để
thiết kế class và phương thức nhằm để trì hoãn chỉ
ra kiểu dữ liệu cho đến khi lớp hoặc phương thức được khai báo hoặc khởi tạo
Một trong những điểm nổi bật của kiểu Generic là cho phép kiểm tra cú pháp trong lúc biên dịch Có thể sử dụng nhiều kiểu dữ liệu khác nhau với cùng
1 đoạn code (tương tự như khái niệm Template
trong C++)
Trang 7 Bằng cách sử dụng tham số T là tham số chung, ta có thể tạo 1 class duy nhất mà khi tham chiếu tới sẽ không gặp bất kỳ lỗi nào xảy ra trong lúc runtime khi ép kiểu hoặc boxing (chuyển giá trị từ value type sang reference type)
public class GenericStack < T >
Trang 9Generic Delegates
Tương tự với method, delegate cũng có thể cài đặt theo kiểu generic (nhằm làm tham số hàm chung nhất cho một số trường hợp)
public delegate void MyDelegate <T>(T param );
Trong NET được khai báo sẵn một số các
generic delegate như: Predicate<T>, Action<T>, Comparison<T>,…
Trang 10 Predicate<T> được sử dụng để kiểm tra các giá trị
có thỏa mãn một điều kiện nào đó không và trả về kiểu bool
Action<T> sử dụng để thực hiện các hành động
với đối tượng mà ta truyền vào và không trả về giá trị nào cả
Comparison<T> dùng để so sánh hai đối tượng
cùng kiểu, thường sử dụng trong các trường hợp sắp xếp
…
Ví dụ thường gặp nhất là các phương thức tĩnh
được cung cấp trong class Array
Trang 11 Ví dụ class Array
T [] Array FindAll < T >( T [] array , Predicate < T > match )
Trang 12bool FuncPredicate ( int value )
{
return value % 2 == 0;
}
StringBuilder sb = new StringBuilder ();
void FuncAction ( int value )
Trang 14 Áp dụng trong các Generic Delegate
StringBuilder sb = new StringBuilder ();
Trang 15Lambda Expression
expression được coi là một sự cái tiến đáng giá từ
phiên bản C# 2.0 lên C# 3.0
in-line nhằm hạn chế việc khai báo các hàm riêng lẻ không cần thiết, giúp mã lệnh ngắn gọn hơn
dàng hơn nhờ việc cung cấp toán tử và cú pháp mới, đồng thời thể hiện sự “thông minh” của compiler bằng cách tự nhận diện kiểu của dữ liệu
expression tree
Trang 16 Dạng của Lambda Expression như sau:
(input parameters) => expression
Dấu mở và đóng ngoặc là tùy chọn trong trong trường
hợp chỉ có 1 tham số, ngược lại nó là bắt buộc
Nếu có nhiều hơn 1 tham số thì chúng sẽ được phân
Trang 17 Trong lambda expression sử dụng toán tử =>,
mang ý nghĩa như là “đi đến”
Phía bên trái của toán tử là các tham số (nếu có), bên phải là các biểu thức hay câu lệnh Ví dụ:
Trang 18 Sử dụng trong Generic Delegate
StringBuilder sb = new StringBuilder ();
Trang 19Delegate Func<…>
Từ C# 3.0, cùng với việc ra đời của lambda
expression, Microsoft cung cấp cho ta một kiểu delegate mới linh hoạt và tiện dụng hơn, tên của kiểu delegate này là Func
Func cho phép khai báo và tạo ra các dạng
delegate với số lượng tham số và kiểu trả về
khác nhau, tương tự như khi tạo ra một phương thức
Func được dùng chủ yếu để tạo và lưu trữ một anonymous method ngắn gọn bằng lambda
expression và được sử dụng như những phương thức thông thường
Trang 20 Cú pháp để sử dụng Func là viết các kiểu của tham
số và giá trị trả về vào cặp ngoặc ‘<>’, theo sau từ
khóa Func
Func < T , T , , TResult>
Trong đó T là các kiểu của tham số cần truyền vào và TResult là kiểu của giá trị trả về
Lưu ý là Func yêu cầu ít nhất một tham số trong cặp
‘<>’, tức là phải có kiểu trả về Không để đặt void hay để một cặp ngoặc ‘<>’ rỗng khi dùng Func
Trang 21Multithread
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, hoàn toà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
Trang 22Ngữ 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
Thiết lập độ ưu tiên
Hoạt động tiêu tốn nhiều thời gian không dừng toàn bộ ứng dụng
Copy file dung lượng lớn!
Import file vào CSDL
…
Trang 24Tạo một Thread
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 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
ThreadStart chứa phương thức sẽ thực thi vào
constructor của Thread
thread vừa tạo
Trang 26});
Thread t2 = new Thread (() => { for ( int i = 0; i < 100; ++ i ) Console Write ( "B" );
});
t1 Start ();
t2 Start ();
}
Trang 27Truyề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ố
Trang 29});
t1 Start ( "A" );
t2 Start ( "B" );
}
Trang 30Property quan trọng của Thread
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,…
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
Trang 31Các phương thức thông dụng của
Thread
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
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
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ế
Trang 32 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
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 đó hoàn tất công
việc của mình
Trang 33t2 Start ( new object [] { "B", 100 });
object [] arrObj = ( object []) param ;
string str = ( string ) arrObj [0];
int n = ( int ) arrObj [1];
Trang 34Foreground và Background Thread
thực thi cho đến khi kết thúc mặc dù chương
trình (Thread chính) đã hoàn thành (hoặc bị bắt buộc ngừng) và kết thúc
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
Trang 36Thread 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
đặt ở chế độ background (Background Thread)
phương thức tĩnh QueueUserWorkItem() của lớp
ThreadPool
Trang 39Giả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
Trang 40 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 (); }
Trang 41Deadlock
Đồ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
đợ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
Trang 42 Ví dụ
static int m = 0, n = 0;
static object syncM = new object ();
static object syncN = new object ();
static void Main ( string [] args )