Nhập môn đa luồng và lệnh gọi lại

Khoá học Phát triển ứng dụng Android bằng Kotlin giả định rằng bạn đã quen thuộc với các khái niệm và thuật ngữ về đa luồng (multi-threading). Trang này cung cấp thông tin tổng quan và giúp bạn gợi nhớ.

Thiết bị di động có bộ xử lý và ngày nay, hầu hết thiết bị đều có nhiều bộ xử lý phần cứng và các bộ xử lý đó chạy đồng thời với nhau. Quá trình này gọi là xử lý đa nhiệm (multiprocessing).

Để sử dụng bộ xử lý một cách hiệu quả hơn, hệ điều hành có thể cho phép một ứng dụng tạo nhiều luồng thực thi trong một quy trình. Phương thức này được gọi là đa luồng (multi-threading).

hình ảnh

Bạn có thể hình dung việc này như sau: đọc nhiều cuốn sách cùng một lúc, chuyển đổi giữa các cuốn sách sau mỗi chương, cuối cùng thì đọc hết tất cả các cuốn sách. Tuy nhiên, bạn không thể đọc nhiều cuốn sách cùng một lúc.

Cần một chút hạ tầng để quản lý tất cả các luồng đó.

hình ảnh

Trình lập lịch biểu xem xét những yếu tố như mức độ ưu tiên và đảm bảo mọi luồng đều chạy và kết thúc. Không có cuốn sách nào được phép nằm phủi bụi trong kệ mãi, nhưng đối với một cuốn sách rất dài hoặc có thể chờ đợi, thì có thể mất một chút thời gian để cuốn sách được gửi đến bạn.

Trình điều phối (dispatcher) thiết lập luồng, tức là gửi cho bạn những cuốn sách bạn cần đọc và chỉ định một ngữ cảnh để diễn ra việc đó. Bạn có thể coi ngữ cảnh là một phòng đọc chuyên biệt và riêng biệt. Có một số ngữ cảnh phù hợp với thao tác trên giao diện người dùng trong khi một số ngữ cảnh khác lại chuyên dùng để xử lý các thao tác đầu vào/đầu ra.

Còn một điều nữa mà bạn cần biết là ứng dụng dành cho người dùng thường có một luồng chính chạy ở chế độ nền trước và có thể điều phối các luồng khác chạy ở chế độ nền.

Trên Android, luồng chính là một luồng duy nhất xử lý tất cả bản cập nhật cho giao diện người dùng. Luồng chính này (còn gọi là luồng giao diện người dùng) cũng là luồng gọi tất cả trình xử lý lượt nhấp cũng như các lệnh gọi lại giao diện người dùng/vòng đời khác. Luồng giao diện người dùng là luồng mặc định. Trừ phi ứng dụng của bạn chuyển đổi luồng một cách rõ ràng hoặc sử dụng một lớp chạy trên một luồng khác, mọi việc mà ứng dụng của bạn thực hiện đều nằm trên luồng chính.

Điều này tạo ra một thách thức tiềm ẩn. Luồng giao diện người dùng phải chạy mượt mà để đảm bảo mang lại trải nghiệm người dùng tuyệt vời. Để người dùng nhìn thấy ứng dụng mà không gặp phải tình trạng tạm dừng, luồng chính phải cập nhật màn hình chậm nhất sau mỗi 16 ms, hoặc ở mức khoảng 60 khung hình/giây. Ở tốc độ này, con người nhận thấy việc thay đổi khung hình hoàn toàn mượt mà. Như vậy là rất nhiều khung và ít thời gian. Do đó, trên Android, bạn phải tránh việc chặn luồng giao diện người dùng. Chặn trong ngữ cảnh này nghĩa là luồng giao diện người dùng không làm gì trong khi chờ một nội dung nào đó chẳng hạn như cơ sở dữ liệu hoàn tất việc cập nhật.

hình ảnh

Có nhiều thao tác phổ biến mất hơn 16 mili giây, chẳng hạn như tìm nạp dữ liệu từ Internet, đọc tệp lớn hoặc ghi dữ liệu vào cơ sở dữ liệu. Do đó, việc gọi mã để thực hiện các thao tác như những việc cần làm qua luồng chính có thể khiến ứng dụng tạm dừng, bị giật hoặc thậm chí bị treo. Nếu bạn chặn luồng chính trong thời gian quá dài, ứng dụng có thể gặp sự cố và cho thấy hộp thoại "ứng dụng không phản hồi" (ANR).

Lệnh gọi lại

Bạn có nhiều cách để hoàn thành công việc qua luồng chính.

Có một mẫu để thực hiện các thao tác dài hạn mà không chặn luồng chính, đó là lệnh gọi lại. Bằng cách sử dụng các lệnh gọi lại, bạn có thể bắt đầu các thao tác dài hạn trên một luồng ở chế độ nền. Khi thao tác hoàn tất, lệnh gọi lại (cung cấp dưới dạng đối số) sẽ được gọi để thông báo mã kết quả trên luồng chính.

Lệnh gọi lại là một mẫu tuyệt vời, nhưng cũng có một vài hạn chế. Mã sử dụng nhiều lệnh gọi lại có thể trở nên khó đọc và khó hiểu hơn. Lý do là tuy mã trông có dạng tuần tự, nhưng mã gọi lại sẽ chạy vào một số thời điểm không đồng bộ trong tương lai. Ngoài ra, lệnh gọi lại không cho phép sử dụng một số tính năng ngôn ngữ, chẳng hạn như ngoại lệ.

Coroutine

Trong Kotlin, coroutine là giải pháp để xử lý các thao tác dài hạn một cách nhã nhặn và hiệu quả. Coroutine Kotlin cho phép bạn chuyển đổi mã dựa trên lệnh gọi lại thành mã tuần tự. Mã được viết tuần tự thường dễ đọc hơn và thậm chí có thể sử dụng các tính năng ngôn ngữ như ngoại lệ. Rốt cục thì coroutine và lệnh gọi lại hoạt động giống hệt nhau: chờ cho đến khi có kết quả từ một thao tác dài hạn rồi tiếp tục thực thi.