Lập trình đồng bộ Synchronous
Code chạy trong Dart
là chạy trên một luồng (thread),
dòng code bạn viết nó thi hành hết câu lệnh này sang câu lệnh khác. Nên một
khối lệnh nào đó khóa thread (làm tắc thread) thì toàn bộ ứng dụng bị treo.
Hãy xem một chương trình đơn giản, mã thông thường (Synchronous) như sau
const info = '#4fs358wredsfadsfdfdw'; getInfomation() { return info; } showInfomation() { var data = getInfomation(); print('This is your data -' + DateTime.now().toString()); print(data); } secondFunction() { print('Thời gian - ' + DateTime.now().toString()); } main() { showInfomation(); secondFunction(); }
//Kết quả chạy This is your data -2018-12-10 15:39:08.804717 #4fs358wredsfadsfdfdw Thời gian - 2018-12-10 15:39:08.810717
Logic khi chạy chương trình đó là thi hành hàm showInfomation()
, khi hàm này
hoàn thành tác vụ của mình (có gọi một hàm khác getInfomation
để lấy
dự liệu rồi in dữ liệu đó ra) thì chương trình tiếp tục chạy chàm secondFunction()
Lập trình bất đồng bộ - ASynchronous
Giả sử ở code trên nếu showInfomation()
(hoặc getInfomation()
)
mất nhiều thời gian để hoàn thành (10s, 20s ... ví dụ khi đọc file, thực hiện
http request ...) thì các khối lệnh khác (hàm secondFunction()
) cứ phải
chờ nó hoàn thành mới được thi hành.
Trong tình huống này, nếu mong muốn trong khi showInfomation()
đang thực hiện
công việc của mình thì khối lệnh khác - hàm secondFunction()
vẫn được thi hành
thì sao ? Lúc đó cần đến kỹ thuật lập trình bất đồng bộ với Dart!
Lập trình bất đồng bộ với Dart bạn cần nắm vững keyword
async
, await
và lớp Future
Cơ chế bất đồng bộ là chương trình cho phép phân nhánh quá trình code hoạt động, làm cho có cảm giác như đa luồng (thực chất vẫn là 1 thread) - có lúc thì chạy code ở nhánh này, có lúc thì chạy code ở nhánh khác - cảm giác thi hành 2 ba việc đồng thời
Hàm bất đồng bộ - async
Hàm bất đồng bộ được khai báo có từ khóa async
phía sau, và đối
tượng trả về của hàm là Future<T>
, với T
là kiểu biểu thức return
trả về.
Ví dụ khai báo một hàm là hàm bất đồng bộ
Future<int> functionName() async { return 1; }
Nếu hàm đó đã khai báo là bất đồng bộ async
thì trong hàm có thể sử dụng
thêm từ khóa await biểu_thức;
cho biết, chờ cho biểu thức thi hành xong mới
thi hành các code tiếp theo của hàm. Ví dụ:
Future<int> functionName() async { await biểu_thức; //ví dụ await getInfomation(); return 1; }
Khi hàm đã là bất đồng bộ, lời gọi đến hàm nó sẽ trả về đối tượng Future, để bạn có thể thiết lập các tác vụ khi hàm đó thi hành xong.
Trở lại ví dụ trên, ta sẽ chuyển showInfomation
thành hàm bất đồng bộ,
để xem kết quả ra sao.
Future<void> showInfomation() async { var data = await getInfomation(); print('This is your data -' + DateTime.now().toString()); print(data); }
Ta đã sử lại showInfomation
biến nó thành hàm bất đồng bộ
do cho thêm từ khóa async
, và trả về Future<void>,
nghĩa là không chứa dữ liệu gì khi hàm đó hoàn thành. Trong code thì cũng
sử dụng await getInfomation()
để cho biết hàm đó hoàn thành rồi
mới chạy các code phía dưới (print).
Giờ chạy lại chương trình, kết quả sẽ là:
main() { showInfomation(); secondFunction(); } Thời gian - 2018-12-10 16:05:27.800742 This is your data -2018-12-10 16:05:27.812741 #4fs358wredsfadsfdfdw
Ta thấy, trong khi showInfomation
đang chạy chờ hoàn thành, thì secondFunction
cũng được khởi chạy, dự liệu của nó còn hiện ra trước.
Sử dụng Future
Trả về từ hàm async
là một đối tượng Future, từ đối tượng này
cho phép sử lý kết quả trả về khi hàm async
hoàn thành. Giờ ta sửa code
showInfomation() trả về dữ liệu Future<string> khi hàm đó hoàn thành nhiệm vụ.
Future<String> showInfomation() async { var data = await getInfomation(); print('This is your data -' + DateTime.now().toString()); print(data); return 'showInfomation Complete!'; //Trả về chuỗi - chứa trong Future }
Khi gọi hàm, ta lấy được đối tượng Future, ví dụ:
main() { Future f = showInfomation(); secondFunction(); }
Từ đối tượng này, ta có thể gán các hàm callback, được chạy mỗi khi dữ liệu hoàn thành
trả về bởi hàm async
, để gán hàm callback sử dụng phương thức:
Future.then((data) => hamcallback(data));
Hàm callback đưa vào Future.then phải khai báo có 1 tham số, để có thể sử lý được dữ liệu:
callbackFuture(dynamic data) { }
Ví dụ trên, sửa thêm thành:
notifyFinish(String s) { print(s); } main() { Future f = showInfomation(); f.then((data) => notifyFinish(data)); secondFunction(); }
Kết quả chạy thử:
Thời gian - 2018-12-10 16:25:54.125748 This is your data -2018-12-10 16:25:54.132747 #4fs358wredsfadsfdfdw showInfomation Complete!
Nếu đoạn mã của hàm callback ngắn, thì khai báo luôn dạng closure
main() { Future f = showInfomation(); f.then((data) =>(data) { print(data); }); secondFunction(); }
Bắt lỗi
Nếu muốn bắt lỗi trong hàm asyn cũng dùng biểu thức try ... catch
Future<String> showInfomation() async { var data; try { data = await getInfomation(); } catch (e) { //Xử lý lỗi }; print('This is your data -' + DateTime.now().toString()); print(data); return 'showInfomation Complete!'; }
Nếu không bắt lỗi trực tiếp từ async, có thể bắt lỗi ở Future, đây là ví dụ:
main() { Future f = showInfomation(); f.then((data) =>(data) { print(data); }) .catchError((e) => print('Lỗi xảy ra - '+e.toString())); secondFunction(); }
Bạn hãy throw
một Exception
trong hàm getInfomation
,
hoặc showInfomation
để xem lỗi bắt được.
Ví dụ phát sinh lỗi ở hàm getInfomation
getInfomation() { for (int i = 1; i<=1000; i++); throw new Exception('Không lấy được thông tin'); return info; }
Chạy thử xem:
Thời gian - 2018-12-10 16:52:11.290728 Lỗi xảy ra - Exception: Không lấy được thông tin