(Post 20/11/2007) Trong Phần 1, chúng tôi đã mô tả cách truyền dữ liệu vào một luồng, bằng cách sử dụng ParameterizedThreadStart. Đôi khi bạn cần phải làm theo cách khác và lấy lại các giá trị từ một luồng khi nó kết thúc quá trình thực thi. Các đại biểu không đồng bộ cung cấp một cơ chế thuận tiện cho việc này, cho phép bất kỳ số lượng đối số đã nhập nào được chuyển theo cả hai hướng. Hơn nữa, các ngoại lệ chưa được xử lý trên các đại biểu không đồng bộ được ném lại một cách thuận tiện trên luồng gốc và do đó không cần xử lý rõ ràng. Các đại biểu không đồng bộ cũng cung cấp một cách khác vào nhóm luồng.

Cái giá bạn phải trả cho tất cả những điều này là theo mô hình không đồng bộ của nó. Để xem điều này có nghĩa là gì, trước tiên chúng ta sẽ thảo luận về mô hình lập trình thông thường, đồng bộ hơn. Giả sử chúng tôi muốn so sánh hai trang web. Chúng tôi có thể đạt được điều này bằng cách tải xuống từng trang theo trình tự, sau đó so sánh kết quả đầu ra của chúng như sau:

static void ComparePages () {
WebClient wc = new WebClient ();
string s1 = wc.DownloadString (“http://www.oreilly.com”);
string s2 = wc.DownloadString (“http://oreilly.com”);
Console.WriteLine (s1 == s2? “Same”: “Other”);
}

Tất nhiên sẽ nhanh hơn nếu tải cả hai trang cùng một lúc. Một cách để xem vấn đề là đổ lỗi cho DownloadString vì đã chặn phương thức gọi trong khi trang đang tải xuống. Sẽ thật tuyệt nếu chúng ta có thể gọi DownloadString theo kiểu không đồng bộ không chặn, nói cách khác:

  1. Chúng tôi yêu cầu DownloadString bắt đầu thực thi.
  2. Chúng tôi thực hiện các tác vụ khác trong khi nó hoạt động, chẳng hạn như tải xuống một trang khác.
  3. Chúng tôi yêu cầu DownloadString cho kết quả của nó.
Lớp WebClient thực sự cung cấp một phương thức tích hợp được gọi là DownloadStringAsync cung cấp chức năng giống như không đồng bộ. Hiện tại, chúng tôi sẽ bỏ qua điều này và tập trung vào cơ chế mà bất kỳ phương thức nào có thể được gọi là không đồng bộ.

Bước thứ ba là những gì làm cho các đại biểu không đồng bộ trở nên hữu ích. Người gọi gặp nhân viên để nhận kết quả và cho phép bất kỳ trường hợp ngoại lệ nào được ném lại. Nếu không có bước này, chúng ta có đa luồng bình thường. Mặc dù có thể sử dụng các đại biểu không đồng bộ mà không có điểm hẹn, nhưng bạn sẽ thu được ít lợi ích khi gọi ThreadPool.QueueWorkerItem hoặc sử dụng BackgroundWorker.

Đây là cách chúng tôi có thể sử dụng ủy quyền không đồng bộ để tải xuống hai trang web, đồng thời thực hiện một phép tính:

chuỗi đại biểu DownloadString (chuỗi tiểu);

static void ComparePages () {

// Khởi tạo đại biểu bằng chữ ký của
DownloadString : DownloadString download1 = new WebClient (). DownloadString;
DownloadString download2 = new WebClient (). DownloadString;

// Bắt đầu tải xuống:
IAsyncResult cookie1 = download1.BeginInvoke (uri1, null, null);
IAsyncResult cookie2 = download2.BeginInvoke (uri2, null, null);

// Thực hiện một số phép tính ngẫu nhiên:
double seed = 1.23;
for (int i = 0; i <1000000; i ++) seed = Math.Sqrt (seed + 1000);

// Nhận kết quả tải xuống, chờ hoàn thành nếu cần.
// Đây là nơi bất kỳ ngoại lệ nào sẽ được ném ra:
string s1 = download1.EndInvoke (cookie1);
string s2 = download2.EndInvoke (cookie2);

Console.WriteLine (s1 == s2? “Same”: “Other”);
}

Chúng ta bắt đầu bằng cách khai báo và khởi tạo các đại biểu cho các phương thức chúng ta muốn chạy không đồng bộ. Trong ví dụ này, chúng ta cần hai đại biểu để mỗi người có thể tham chiếu đến một đối tượng WebClient riêng biệt (WebClient không cho phép truy cập đồng thời — nếu có, chúng ta có thể sử dụng một đại biểu duy nhất trong suốt).

Sau đó chúng tôi gọi là BeginInvoke. Điều này bắt đầu thực hiện trong khi ngay lập tức trả lại quyền kiểm soát cho người gọi. Theo ủy quyền của chúng tôi, chúng tôi phải chuyển một chuỗi cho BeginInvoke (trình biên dịch thực thi điều này, bằng cách tạo các phương thức BeginInvoke và EndInvoke đã nhập trên kiểu ủy quyền).

BeginInvoke yêu cầu thêm hai đối số — một đối tượng dữ liệu và gọi lại tùy chọn; chúng có thể được để trống vì chúng thường không bắt buộc. BeginInvoke trả về một đối tượng IASynchResult hoạt động như một cookie để gọi EndInvoke. Đối tượng IASynchResult cũng có thuộc tính IsCompleted có thể được sử dụng để kiểm tra tiến độ.

Sau đó, chúng tôi gọi EndInvoke trên các đại biểu, vì kết quả của họ là cần thiết. EndInvoke đợi, nếu cần, cho đến khi phương thức của nó kết thúc, sau đó trả về giá trị trả về của phương thức như được chỉ định trong ủy quyền (trong trường hợp này là chuỗi). Một tính năng thú vị của EndInvoke là nếu phương thức DownloadString có bất kỳ tham số ref hoặc out nào, chúng sẽ được thêm vào chữ ký của EndInvoke, cho phép nhiều giá trị được gửi lại bởi người gọi.

Nếu tại bất kỳ thời điểm nào trong quá trình thực thi phương thức không đồng bộ gặp phải một ngoại lệ chưa xử lý, nó sẽ được ném lại trên luồng của người gọi khi gọi EndInvoke. Điều này cung cấp một cơ chế gọn gàng để sắp xếp các ngoại lệ trở lại người gọi.

Nếu phương thức bạn đang gọi không đồng bộ không có giá trị trả về, thì (về mặt kỹ thuật) bạn vẫn có nghĩa vụ gọi EndInvoke. Theo nghĩa thực tế, điều này mở ra cho việc giải thích; MSDN mâu thuẫn về vấn đề này. Tuy nhiên, nếu bạn chọn không gọi EndInvoke, bạn sẽ cần phải xem xét xử lý ngoại lệ trên phương thức worker.

Phương thức không đồng bộ

Một số loại trong .NET Framework cung cấp phiên bản không đồng bộ của các phương thức của chúng, với tên bắt đầu bằng “Begin” và “End”. Chúng được gọi là các phương thức không đồng bộ và có các chữ ký tương tự như các ký hiệu của các đại biểu không đồng bộ, nhưng tồn tại để giải quyết một vấn đề khó hơn nhiều: cho phép nhiều hoạt động đồng thời hơn bạn có luồng. Ví dụ, một máy chủ cổng kết nối web hoặc TCP có thể xử lý hàng trăm yêu cầu đồng thời chỉ trên một số ít các luồng tổng hợp nếu được viết bằng NetworkStream.BeginRead và NetworkStream.BeginWrite.

Tuy nhiên, trừ khi bạn đang viết một ứng dụng đồng thời cao chuyên biệt, bạn nên tránh các phương thức không đồng bộ vì một số lý do:

  • Không giống như các đại biểu không đồng bộ, các phương thức không đồng bộ có thể không thực sự thực thi song song với trình gọi
  • Lợi ích của các phương pháp không đồng bộ sẽ bị xói mòn hoặc biến mất nếu bạn không tuân theo mô hình một cách tỉ mỉ
  • Mọi thứ có thể trở nên phức tạp nhanh chóng khi bạn làm theo đúng mô hình

Nếu bạn chỉ đơn giản là sau khi thực hiện song song, tốt hơn bạn nên gọi phiên bản đồng bộ của phương thức (ví dụ: NetworkStream.Read) thông qua một đại biểu không đồng bộ. Một tùy chọn khác là sử dụng ThreadPool.QueueUserWorkItem hoặc BackgroundWorker — hoặc chỉ cần tạo một luồng mới.

Lớp WebClient thực sự cung cấp một phương thức tích hợp được gọi là DownloadStringAsync cung cấp chức năng giống như không đồng bộ. Hiện tại, chúng tôi sẽ bỏ qua điều này và tập trung vào cơ chế mà bất kỳ phương thức nào có thể được gọi là không đồng bộ.

Sự kiện không đồng bộ

Một mẫu khác tồn tại theo đó các kiểu có thể cung cấp các phiên bản không đồng bộ của các phương thức của chúng. Đây được gọi là “mẫu không đồng bộ dựa trên sự kiện” và được phân biệt bởi một phương thức có tên kết thúc bằng “Không đồng bộ” và một sự kiện tương ứng có tên kết thúc bằng “Đã hoàn thành”. Lớp WebClient sử dụng mẫu này trong phương thức DownloadStringAsync của nó. Để sử dụng nó, trước tiên bạn xử lý sự kiện “Đã hoàn thành” (ví dụ: DownloadStringCompleted) và sau đó gọi phương thức “Async” (ví dụ: DownloadStringAsync). Khi phương thức kết thúc, nó gọi trình xử lý sự kiện của bạn. Thật không may, việc triển khai của WebClient có sai sót: các phương pháp như DownloadStringAsync chặn người gọi trong một phần thời gian tải xuống.

Mẫu dựa trên sự kiện cũng cung cấp các sự kiện để báo cáo tiến độ và hủy bỏ, được thiết kế để thân thiện với các ứng dụng Windows cập nhật biểu mẫu và điều khiển. Tuy nhiên, nếu bạn cần những tính năng này trong một kiểu không hỗ trợ mô hình không đồng bộ dựa trên sự kiện (hoặc không hỗ trợ nó một cách chính xác!), Bạn không phải tự mình gánh vác việc triển khai mô hình đó (và bạn sẽ không ‘t muốn!) Tất cả những điều này có thể đạt được đơn giản hơn với lớp trợ giúp BackgroundWorker.

(Sưu tầm)

0981578920
icons8-exercise-96