Dev With Ethan

Blog Lập trình cùng Ethan

[Swift] Đa nhiệm với Grand Central Dispatch: Lập lịch và xếp hàng các tác vụ song song

Tiếp theo phần 1, phần 2 này sẽ nói về những ứng dụng quan trọng khác của GCD, bao gồm lập lịch và xếp hàng các tác vụ song song

3. Lập lịch để chạy các tác vụ sau 1 thời gian nhất định

Trong thực tế, có nhiều tình huống chúng ta cần phải xử lý logic sau 1 khoảng thời gian nhất định, ví dụ như khóa cửa rồi chờ 3 giây mới đi ngủ, hoặc ngủ dậy rồi chờ 1 phút mới gấp chăn màn,…

GCD giúp chúng ta làm các việc này 1 cách khá đơn giản:

1
2
3
4
5
6
7
8
let delay = 3.0           // 3 seconds
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))

print("Start waiting")
dispatch_after(dispatchTime, dispatch_get_main_queue()) { 
  print("Perform after \(delay) seconds")
  print("End waiting")
}
Swift code snippet 1
Tác vụ có delay

Ở đây ta đặt delay có giá trị là 3, tức là sau 3 giây sẽ thực hiện tác vụ. Tuy nhiên GCD sử dụng 1 kiểu dữ liệu có tên là dispatch_time_t để làm việc hiệu quả đến nano-seconds, nên ta cần đổi delay thành dispatchTime bằng hàm dispatch_time(). Có 1 lưu ý là chúng ta cần phải đổi delay ở đơn vị giây sang thành đơn vị nano-giây bằng phép tính Int64(delay * Double(NSEC_PER_SEC)). Cuối cùng, gọi hàm dispatch_after() và truyền vào các tham số:

  • dispatchTime: thời gian cần làm trễ (tất nhiên rồi!)
  • Queue sẽ được gọi: ở đây chúng ta gọi dispatch_get_main_queue() tức là ta sẽ vẫn làm việc tại main-thread, không cần phải dùng background-thread gì cả.
  • Cuối cùng là 1 block định nghĩa các thao tác ta cần.

Trong ví dụ này, ta sẽ nhận được kết quả in ra tại màn hình Debug như sau:

1
2
3
Start waiting
Perform after 3.0 seconds
End waiting
Text snippet 1
Kết quả chạy tác vụ có delay

4. Xếp hàng các tác vụ tại các thread khác nhau

Bài toán hay gặp nhất của thể loại này đấy là tương tác với dữ liệu từ server: giả sử chúng ta có 10 APIs cần phải gọi (từ A1 -> A10), trong đó A5, A6, A7, A8 phải chạy sau A1 (tức là A1 xong mới được chạy), A9, A10 phải chạy sau A2, A3, A4 thì chạy tùy ý, thế nào cũng được.

Như vậy ta có thể biểu diễn nó dưới dạng các luồng song song như sau:

  • Luồng 1: A1 -> A5 -> A6 -> A7 -> A8
  • Luồng 2: A2 -> A9 -> A10
  • Luồng 3: A3
  • Luồng 4: A4

Vậy phải làm sao để GCD có thể nhận diện và chạy đúng các tác vụ này? Có 2 cách làm: sử dụng GCD hoặc sử dụng NSOperationQueue. Cách làm bằng GCD tương đối phức tạp, còn bản thân NSOperationQueue được xây dựng dựa trên khá nhiều API của GCD, vậy nên trong phần này chúng ta sẽ dành 1 chút thời gian cho NSOperationQueue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func invokeTask(order: Int, inQueue queueNumber: Int) -> NSOperation {
  let operation = NSOperation()
  operation.queuePriority = .Normal
  operation.qualityOfService = .Background
  
  operation.completionBlock = { print("\t[Queue #\(queueNumber)] Task #\(order) completed!") }
  return operation
}

func createQueue(number: Int, taskOrders: Int...) -> NSOperationQueue {
  let queue = NSOperationQueue()
  queue.maxConcurrentOperationCount = 1
  
  print("Start queue #\(number)")
  taskOrders.forEach { queue.addOperation(invokeTask($0, inQueue: number)) }
  return queue
}

createQueue(1, taskOrders: 1, 5, 6, 7, 8)
createQueue(2, taskOrders: 2, 9, 10)
createQueue(3, taskOrders: 3)
createQueue(4, taskOrders: 4)
Swift code snippet 2
Xếp hàng tác vụ

Ở đây chúng ta có 2 hàm invokeTask()createQueue. Task là các tác vụ A1 -> A10, sử dụng kiểu dữ liệu NSOperation queue là các luồng (từ 1 -> 4), sử dụng kiểu dữ liệu NSOperationQueue. Tại mỗi queue, ta đặt maxConcurrentOperationCount1, tức là ta bắt buộc các tác vụ phải chạy nối đuôi nhau chứ không được song song (ta đã tạo 4 luồng song song rồi, nếu mỗi tác vụ trong từng luồng cũng song song nữa thì sẽ rất loạn!). Kết quả in ra sẽ như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Start queue #1
Start queue #2
Start queue #3
Start queue #4
	[Queue #3] Task #3 completed!
	[Queue #1] Task #1 completed!
	[Queue #2] Task #2 completed!
	[Queue #2] Task #9 completed!
	[Queue #4] Task #4 completed!
	[Queue #1] Task #5 completed!
	[Queue #2] Task #10 completed!
	[Queue #1] Task #6 completed!
	[Queue #1] Task #7 completed!
	[Queue #1] Task #8 completed!
Text snippet 2
Kết quả Xếp hàng tác vụ

Như vậy, ngay từ đầu, cả 4 queue (1, 2, 3, 4) sẽ được tạo & bắt đầu chạy. Task 3 xong trước, thuộc queue 3. Tiếp đến là task 1, thuộc queue 1,… Ta để ý thấy: các tác vụ 1, 5, 6, 7, 8 luôn chạy theo thứ tự, còn 2, 9, 10 cũng thế. Tất nhiên chúng trộn lẫn với nhau, nhưng luôn theo thứ tự mà ta muốn.

Các bạn có thể tải về project GCD Demo để cùng xem các ví dụ về Grand Central Dispatch đã trình bày trong bài.