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
Đăng ký theo dõi ủng hộ kênh