Grand Central Dispatch (Part 1)

by Hoang Anh Tuan
1.4K views

Tổng quan:

Để app có UI, UX mượt mà và nhanh, ta cần chia các tasks cần nhiều thời gian chạy ra các luồng khác để thực hiện -> GCD là 1 trong các cách giúp các bạn quản lí các luồng đó.

Nội dung bài viết:

  • 1 số khái niệm cơ bản về luồng, hàng đợi.
  • Cách tạo luồng bằng GCD.
  • Các vấn đề hay gặp phải trong quản lí luồng.
  • Các basic func của GCD.

Giới thiệu 1 số khái niệm cơ bản về luồng, hàng đợi:

Thread

  • Luồng là nơi các task được thực hiện.
  • 1 app hoặc 1 chương trình sẽ có 1 hoặc nhiều luồng.
  • Mỗi luồng có thể thực hiện đồng thời, tuy nhiên nó tùy thuộc vào hệ thống để biết khi nào nó được thực hiện và được thực hiện thế nào.
  • GCD được xây dựng dựa trên các luồng. Với GCD, bạn đưa các task vào dispatch queue và GCD sẽ tự quyết định luồng nào được dùng để thực hiện các task.

Queue

  • Queue là hàng đợi, hoạt động theo cơ chế FIFO.
  • Bạn đưa các task vào queue, và GCD sẽ thực hiện chúng theo cơ chế FIFO.
  • Bản thân Dispatch Queues đã là thread safe nên bạn có thể đưa task vào queue từ bất kì luồng nào.
  • Vì GCD sẽ tự động chọn luồng để thực hiện các task giúp bạn, nên việc của bạn là chỉ cần chọn loại queue phù hợp để đưa task vào.

Có 2 loại queue là SerialConcurrent.
Serial: hàng đợi chỉ thực hiện 1 task vào 1 thời điểm, task này xong thì thực hiện task tiếp theo.
Concurrent: hàng đợi thực hiện nhiều task tại 1 thời điểm, các task vẫn được thực hiện theo thứ tự FIFO, nhưng bạn sẽ không biết số task thực hiện tại 1 thời điểm hay thời điểm các task hoàn thành.

Serial Queue và Concurrent Queue

Với GCD, bạn có thể thực hiện các task async hoặc sync.
sync: 1 task được thực hiện kiểu synchronous sẽ trả quyền điều khiển cho hàm gọi chúng sau khi task được hoàn thành.
async: 1 task được thực hiện kiểu asynchronus sẽ trả quyền điều khiển ngay lập tức mà không cần đợi phải hoàn thành. Vì vậy, 1 async task sẽ không block luồng hiện tại. Luồng hiện tại sẽ được trả quyền điều khiển để thực hiện task tiếp theo.

Cách tạo queue với GCD:

1. Khởi tạo: GCD cung cấp cách khởi tạo luồng với các quality of Service khác nhau như sau:

Swift gồm 5 loại quality of Service được sử dụng cho các mục đích khác nhau:

Cách gọi ra Main queue – đây là nơi để bạn thực hiện các task updates UI:

DispatchQueue.main

Cách tạo ra 1 private queue: (mặc định là serial queue)

let serialQueue = DispatchQueue(label: "techOver")

Để tạo ra 1 queue kiểu concurrent thì bạn set thuộc tính attribute kiểu concurrent:

let serialQueue = DispatchQueue(label: "techOver", attributes: .concurrent)

2. Thêm task vào các queue với kiểu thực hiện sync hoặc async:

DispatchQueue.global().async {
   // do expensive non-UI task
   DispatchQueue.main.async {
      // do updates UI when task is finished
   }
}

Các vấn đề hay gặp về quản lí luồng:

priority inversion: Các task với priority cao sẽ được thực hiện trước các task có priority thấp, tuy nhiên các task có priority thấp lại lock resource, dẫn đến các task với priority cao phải đợi để được thực hiện -> Làm app chậm.
Race condition: 2 task cùng truy cập để 1 sửa 1 tài nguyên chung trong cùng 1 thời điểm, dẫn đến sai lệch kết quả.

Deadlock: Xảy ra khi 2 tasks đang đợi lẫn nhau giải phóng tài nguyên để có thể thực hiện tiếp -> Dẫn đến việc cả 2 tasks đều không thể thực hiện -> Treo app.

Các basic func của GCD:

1. Dispatch Group: Giả sử bạn có nhiều task được thực hiện theo kiểu async, khi đó bạn sẽ không thể biết khi nào chúng hoàn tất bởi chúng trả về quyền điều khiển ngay lập tức -> Dispatch Group dùng để nhận biết khi 1 nhóm các task được thực hiện xong.

// 1
let dispatchGroup = DispatchGroup()
// 2
let viewContainer = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
viewContainer.backgroundColor = .red
view.addSubview(viewContainer)

// A box move around in the view
let box = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
box.backgroundColor = .yellow
viewContainer.addSubview(box)

// A label is appear when all the animations finish
let label = UILabel(frame: CGRect(x: 15, y: 100, width: 360, height: 40))
label.font = label.font.withSize(50)
label.text = "All Done!"
label.textColor = .yellow
label.textAlignment = .center
label.isHidden = true
viewContainer.addSubview(label)

// Animations
// 3
dispatchGroup.enter()
UIView.animate(withDuration: 2, animations: {
    box.center = CGPoint(x: 300, y: 300)
}, completion: { _ in
    UIView.animate(withDuration: 3, animations: {
        box.transform = CGAffineTransform(rotationAngle: .pi/4)
    }, completion: { _ in
         // 4
         self.dispatchGroup.leave()
    })
})
// 5        
dispatchGroup.notify(queue: .main) {
    UIView.animate(withDuration: 2, animations: {
        viewContainer.backgroundColor = .blue
    }, completion: { _ in
        label.isHidden = false
    })
}

Note: Thử đưa đoạn code này vào viewDidLoad và run thử

  1. Khởi tạo 1 DispatchGroup.
  2. Khởi tạo 1 vài view để thực hiện animation.
  3. Gọi hàm enter() để đưa task cần thực hiện vào trong group.
  4. Gọi hàm leave() để báo với group rằng task đã thực hiện xong.
  5. Khai báo xem hàm notify sẽ làm gì khi các task trong group được thực hiện xong. Hàm notify sẽ được tự động gọi đến khi tất cả các task được đưa vào group đã được thực hiện xong.

Note: Số lần gọi enter() phải bằng số lần gọi hàm leave().

Kết luận: Thay vì phải đặt delay giữa các animation thì chúng ta có thể dùng DispatchGroup để nhận biết khi 1 animation đã thực hiện xong để thực hiện tiếp animation khác.

Ở bài viết tiếp theo sẽ tiếp tục là về các basic func và 1 vài advance func của GCD.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.