1) Sử dụng Điều khiển BackgroundWorker
Điều khiển BackgroundWorker cho phép nhiệm vụ thực hiện trên một thread khác.
- Phương thức void RunWorkerAsync(Object obj) để bắt đầu thực hiện tuyến đoạn. Khi tuyến đoạn bắt đầu thực hiện, sẽ phát sinh sự kiện DoWork
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e){}
Tham đối obj truyền cho phương thức RunWorkerAsync, được nhận bởi e.Argument trong phương thức đáp ứng sự kiện DoWork, và nhận bởi e.Result trong phương thức đáp ứng sự kiện RunWorkerCompleted
- Phương thức ReportProgress(int percentProgress): phát sinh sự kiện ProgressChanged để báo cáo quá trình thực hiện tuyến đoạn. Tham đối percentProgress truyền vào phương thức, được nhận bởi e.ProgressPercentage trong phương thức đáp ứng sự kiện ProgressChanged
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e) {}
- Phương thức CancelAsync(): phát sinh sự kiện RunWorkerCompleted. Sự kiện RunWorkerCompleted phát sinh khi tuyến đoạn hoàn thành thành công, hay hỏng hay hủy tuyến đoạn bởi phương thức CancelAsync()
Ví dụ:Kết hợp ProgressBar với BackgroundWorker
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;
namespace FormProgressBar
{
public partial class FormProgressBar : Form
{
int max;
private void FormProgressBar_Load(object sender, EventArgs e)
{
this.toolStripStatusLabel1.Text = "ProgressBar value";
this.toolStripDropDownButton1.DropDownItems.Add("30");
this.toolStripDropDownButton1.DropDownItems.Add("50");
this.toolStripDropDownButton1.DropDownItems.Add("100");
}
private void button1_Click(object sender, EventArgs e)
{
// Phát sinh sự kiện DoWork
backgroundWorker1.RunWorkerAsync();
this.progressBar1.Maximum = max;
this.progressBar1.Minimum = 0;
this.toolStripProgressBar1.Maximum = max;
this.toolStripProgressBar1.Minimum = 0;
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= max; i++)
{
Thread.Sleep(100);
// Phát sinh sự kiện ProgressChanged
this.backgroundWorker1.ReportProgress(i);
}
}
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text =
"ProgressBar value: "+ e.ProgressPercentage.ToString();
}
private void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
if (max ==0) MessageBox.Show("Chọn Max Value");
else MessageBox.Show("Thực hiện xong tiến trình");
}
private void toolStripDropDownButton1_DropDownItemClicked(
object sender, ToolStripItemClickedEventArgs e)
{
max = Convert.ToInt32(e.ClickedItem.Text);
}
}
}
2) Lập trình xử lý song song sử dụng tuyến đoạn (Thread)
C# cung cấp khả năng lập trình xử lý song song, cho phép thực hiện nhiều nhiệm vụ song song, đồng thời, bằng kỹ thuật tạo tuyến đoạn. Tuyến đoạn là một đơn vị hành động, một chương trình có thể chia nhỏ thành nhiều tuyến đoạn hoạt động đồng thời. Thực tế là các tuyến đoạn được luân phiên gọi đến, nên xem như hoạt động song song.
Mặc định, một chương trình C# có một tuyến đoạn chính (primary thread). Tuyến đoạn này thực hiện mã trong chương trình, bắt đầu và kết thúc bởi phương thức Main. Tuy nhiên, các tuyến đoạn phụ (auxiliary threads hay còn gọi là worker threads) có thể được tạo và thực hiện song song với tuyến đoạn chính.
Chẳng hạn một chương trình có tuyến đoạn chính chứa giao diện người dùng và quản lý các tương tác với người dùng trong khi các tuyến đoạn phụ thực hiện các nhiệm vụ khác như chạy đồng hồ.
Không gian tên System.Threading cung cấp các lớp và giao tiếp hỗ trợ lập trình đa tuyến đoạn (Multithreading). Bạn dễ dàng để thực hiện các nhiệm vụ như tạo và bắt đầu tuyến đoạn mới, truyền dữ liệu dến một tuyến đoạn, đồng bộ hóa nhiều tuyến đoạn, ngừng, chạy lại tuyến đoạn và kết thúc chạy tuyến đoạn.
Để tạo một tuyến đoạn mới, bạn khởi tạo một đối tượng của lớp Thread thuộc không gian tên System.Threading, truyền vào phương thức khởi tạo tuyến đoạn, một phương thức sẽ được gọi khi bắt đầu tuyến đoạn. Sau đó bạn gọi phương thức Start của đối tượng Thread thuộc không gian tên System.Threading để bắt đầu chạy tuyến đoạn
Thread t = new Thread(ĐốiTượng.PhươngThức);
t.Start();
Cách khác, với C# 3.0, có thể sử dụng phương thức nặc danh (anonymous method) như sau:
Thread t = new Thread ( delegate()
{
Console.WriteLine ("Hello"));
});
t.Start();
Hay sử dụng biểu thức lambda (lambda expression) như sau:
Thread t = new Thread (() => Console.WriteLine ("Hello"));
t.Start();
Phương pháp dễ dàng nhất để truyền tham đối đến một phương thức của tuyến đoạn là thực hiện biểu thức lambda gọi phương thức với các tham đối
Thread t = new Thread ( () => Print ("Hello") );
t.Start();
static void Print (string message)
{
Console.WriteLine (message);
}
Bạn có thể thực hiện khối lệnh trong biểu thức lambda:
new Thread (() =>
{
Console.WriteLine ("Tạo tuyến đoạn mới");
Console.WriteLine ("và thực hiện tuyến đoạn");
}).Start();
Ví dụ sau trình bày làm thế nào để tạo một tuyến đoạn và tương tác giữa hai tuyến đoạn chạy đồng thời trong cùng một quá trình. Chương trình hiển thị hai số 1 và 2 trên màn hình, chờ người dùng gõ vào mũi tên phải, hay trái để đổi vị trí của hai số 1 và 2, và đồng thời tạo một tuyến đoạn phụ chạy đồng hồ ở góc phải trên màn hình.
using System;
using System.Threading;
class DongHo
{
public void Show()
{
while (true)
{
Console.CursorLeft = 55;
Console.CursorTop = 1;
Console.WriteLine(DateTime.Now.ToString());
Thread.Sleep(1000);
}
}
}
class Program
{
//Chạy đồng hồ ở tuyến đoạn phụ
public void ChayDongHo()
{
Thread t = new Thread(new DongHo().Show);
t.Start();
}
//Lặp đi lặp lại chờ người dùng nhấn phím di chuyển trái hay phải
//để dịch chuyển hai số ở tuyến đoạn chính
public void DichChuyenSo()
{
Console.SetCursorPosition(1, 5);
Console.Write(1);
Console.SetCursorPosition(5, 5);
Console.Write(2);
while (true)
{
ConsoleKey key = Console.ReadKey(true).Key;
if (key == ConsoleKey.LeftArrow)
{
Console.SetCursorPosition(5, 5);
Console.Write(1);
Console.SetCursorPosition(1, 5);
Console.Write(2);
}
else if (key == ConsoleKey.RightArrow)
{
Console.SetCursorPosition(5, 5);
Console.Write(2);
Console.SetCursorPosition(1, 5);
Console.Write(1);
}
}
}
static void Main(string[] args)
{
Program p = new Program();
p.ChayDongHo();
p.DichChuyenSo();
}
}
Thuộc tính IsAlive của đối tượng thread cho phép chương trình đợi cho đến khi tuyến đoạn được khởi tạo xong. Phương thức Join đợi cho tuyến đoạn thực hiện một khoảng thời gian chỉ rõ trả về false hay cho đến khi tuyến đoạn kết thúc trả về true. Phương thức Abort kết thúc chạy tuyến đoạn.
Ví dụ:Bổ sung đoạn mã sau vào phương thức ChayDongHo để đợi tuyến đoạn thực hiện trong 5 giây
public void ChayDongHo()
{
Thread t = new Thread(new DongHo().Show))
t.Start();
while (!t.IsAlive);
Console.SetCursorPosition(1, 10);
Console.WriteLine("Tuyen doan chinh dung 5 giay");
t.Join(5000);
//t.Join(TimeSpan.FromSeconds(5));
//t.Join(); //đợi đến khi tuyến đoạn thực hiện xong
Console.SetCursorPosition(1, 10);
Console.WriteLine("Tuyen doan chinh da chay lai ");
}
Ngoài ra, có thể sử dụng thư viện nhiệm vụ song song TPL (Task Parallel Library) để phân rã ứng dụng thành các nhiệm vụ thực hiện song song, đồng thời. Để tạo một nhiệm vụ, bạn khởi tạo một đối tượng của lớp System.Threading.Tasks.Task và truyền vào phương thức khởi tạo, một phương thức thực hiện nhiệm vụ. Sau đó, gọi phương thức Start của đối tượng Task, tuyến đoạn sẽ được tạo ra và bắt đầu thực hiện nhiệm vụ.
Ví dụ:Viết lại chương trình hiển thị hai số 1 và 2 trên màn hình, chờ người dùng gõ vào mũi tên phải, hay trái để đổi vị trí của hai số 1 và 2, và đồng thời tạo một tuyến đoạn chạy đồng hồ ở góc phải trên màn hình, sử dụng thư viện TPL.
using System;
using System.Threading.Tasks;
class Program
{
public void DichChuyenSo()
{
Console.SetCursorPosition(1, 5);
Console.Write(1);
Console.SetCursorPosition(5, 5);
Console.Write(2);
while (true)
{
ConsoleKey key = Console.ReadKey(true).Key;
if (key == ConsoleKey.LeftArrow)
{
Console.SetCursorPosition(5, 5);
Console.Write(1);
Console.SetCursorPosition(1, 5);
Console.Write(2);
}
else if (key == ConsoleKey.RightArrow)
{
Console.SetCursorPosition(5, 5);
Console.Write(2);
Console.SetCursorPosition(1, 5);
Console.Write(1);
}
}
}
static void Main(string[] args)
{
Task task1 = new Task(new Action(new DongHo().Show));
task1.Start();
new Program().DichChuyenSo();
}
}
Đa tuyến đoạn giải quyết các vấn đề đa nhiệm vụ (multi-tasking), vấn đề chia sẻ và đồng bộ hóa tài nguyên khi các tuyến đoạn truy cập cùng dữ liệu. Không gian tên System.Threading cung cấp nhiều phương thức để đồng bộ hóa tuyến đoạn.
Các phương thức đợi thực hiện (blocking methods) đơn giản để đợi tuyến đoạn thực hiện một khoảng thời gian hay đợi đến khi tuyến đoạn kết thúc như Sleep, Join và Wait.
task1.Wait(); //đợi tuyến đoạn thực hiện xong
task1.Wait(5000); //đợi tuyến đoạn thực hiện trong 5 giây
task1.Wait(TimeSpan.FromSeconds(5));
Các xử lý khóa (locking constructs) có thể được sử dụng để đảm bảo một khối lệnh thực hiện đến khi xong, không bị ngắt bởi các tuyến đoạn khác. Khi lệnh thực thi, đối tượng thuộc lớp này chỉ chấp nhận làm việc với một tuyến đoạn, các tuyến đoạn khác nếu có làm việc với đối tượng này, thì tạm thời ngưng chạy chờ tới phiên.
Lệnh lock bắt đầu với từ khóa lock, truyền tham đối là đối tượng và theo sau bởi khối lệnh được thực hiện bởi một tuyến đoạn tại một thời điểm.
Object lockThis = new Object();
lock (lockThis) { }
Ngoài ra có các xử lý khóa khác như Monitor.Enter/Monitor.Exit, Mutex, SpinLock, Semaphore, SemaphoreSlim.
Các xử lý tín hiệu (signaling constructs) cho phép một tuyến đoạn khóa cho đến khi nhận một thông báo từ tuyến đoạn khác, như phương thức Wait, Pulse của lớp Monitor. Một tuyến đoạn gọi Wait khi muốn vào trạng thái đợi nó thực hiện và một tuyến đoạn khác gọi Pulse để hủy trạng thái khóa của tuyến đoạn. Các phương thức này phải được gọi trong lệnh lock, ngược lại sẽ phát sinh ngoại lệ SynchronizationLockException.
Object key = new Object();
// thread A
lock (key) Monitor.Wait(key);
// thread B
lock (key) Monitor.Pulse(key);
Ví dụ sau trình bày làm thế nào để đồng bộ hóa tuyến đoạn sử dụng lệnh lock và phương thức Wait, Pulse của lớp Monitor. Xét ví dụ chạy đồng hồ như trên, ta muốn khi đồng hồ bắt đầu chạy sẽ gọi phương thức Wait để đợi tuyến đoạn đồng hồ chạy. Sau 5 giây sẽ gọi phương thức Pulse để hủy trạng thái khóa của tuyến đoạn.
using System;
using System.Threading;
using System.Threading.Tasks;
class Lock
{
public static Object obj = new Object();
}
class DongHo
{
public void Show()
{
int d = 0;
while (true)
{
Console.CursorLeft = 55;
Console.CursorTop = 1;
Console.WriteLine(DateTime.Now.ToString());
Thread.Sleep(1000);
d += 1000;
if (d >= 5000) lock(Lock.obj) Monitor.Pulse(Lock.obj);
}
}
}
class Program
{
//Chạy đồng hồ ở tuyến đoạn phụ
public void ChayDongHo()
{
Thread t = new Thread(new DongHo().Show);
t.Start();
lock (Lock.obj) Monitor.Wait(Lock.obj);
}
//Lặp đi lặp lại chờ người dùng nhấn phím di chuyển trái hay phải
//để dịch chuyển hai số ở tuyến đoạn chính
public void DichChuyenSo()
{
Console.SetCursorPosition(1, 5);
Console.Write(1);
Console.SetCursorPosition(5, 5);
Console.Write(2);
while (true)
{
ConsoleKey key = Console.ReadKey(true).Key;
if (key == ConsoleKey.LeftArrow)
{
Console.SetCursorPosition(5, 5);
Console.Write(1);
Console.SetCursorPosition(1, 5);
Console.Write(2);
}
else if (key == ConsoleKey.RightArrow)
{
Console.SetCursorPosition(5, 5);
Console.Write(2);
Console.SetCursorPosition(1, 5);
Console.Write(1);
}
}
}
static void Main(string[] args)
{
Program p = new Program();
p.ChayDongHo();
p.DichChuyenSo();
}
}
Các xử lý đồng bộ hóa không đợi thực hiện (nonblocking synchronization constructs) gồm có Thread.MemoryBarrier, Thread.VolatileRead, Thread.VolatileWrite, từ khóa volatile và lớp Interlocked.
» Tin mới nhất:
» Các tin khác: