[iOS] GCD (9) - Operation Queues

전성훈·2023년 11월 2일
0

iOS/Concurrency

목록 보기
9/11
post-thumbnail

주제: Operation의 세부사항 관리


Operation

  • Operation의 진정한 힘은 OperationQueue가 작업을 관리할 때 나타난다. GCD의 DispatchQueue와 마찬가지로, OperationQueue 클래스를 사용하여 Operation의 스케줄링과 동시에 실행할 수 있는 최대 Operation 수를 관리할 수 있다.
  • OperationQueue는 세 가지 방법으로 작업을 추가할 수 있다.
    1. Operation을 전달한다.
    2. 클로저를 전달한다.
    3. Operatoin의 배열을 전달한다.
  • Operation은 기본적으로 동기적인 작업을 수행하지만, GCD의 dispatchQueued 작업처럼 OperationQueue를 활용하여 비동기적인 작업(concurrency)을 수행할 수 있다.

OperationQueue management

  • OperationQueue는 준비된 operation을 QoS 값과 operation이 가지는 모든 종속성에 따라 실행한다.
  • Operation을 큐에 추가하면 완료되거나 취소될 때까지 실행된다.
  • Operation을 OperationQueue에 추가하면, 동일한 Operation을 다른 OperationQueue에 추가할 수 없다. Operation 인스턴스는 한 번 수행하고 끝나는 작업이므로, 필요한 경우 여러 번 실행할 수 있도록 서브 클래스를 만든다.

Waiting for completion

  • OperationQueue를 들여다보면 waitUntilAllOperationsAreFinished라는 메소드를 볼 수 있다. 이는 현재의 스레드를 operation 작동이 완료되기 전까지 멈추게 된다. 즉, wait을 block으로 이해하면 쉽다.
  • 결국 위 메서드는 main UI thread에서 절대 불러오면 안 된다.
// operationQueue 생성
let printQueue = OperationQueue() 

// 사용할 최대의 쓰레드 갯수 정의(기본 설정 -1) -> 알아서 
// 1 -> 시리얼 
printQueue.maxConcurrentOperationCount = 2 

// 간단한 클로저를 오퍼레이션 큐에 더함 (더하게 되면, 바로 작업이 비동기적으로 시작)
// 주로, 'operation'을 'operationQueue'에 넣어서 작업을 실행시키겠지만, 단순하게 클로저 가능 
printQueue.addOperation { print("Test") 
						sleep(2)}
printQueue.addOperation { print("Test") 
						 sleep(2)}
printQueue.addOperation { print("Test") 
						sleep(2)}
printQueue.addOperation { print("Test") 
						sleep(2)}

printQueue.waitUntilAllOperationsAreFinished()
print("OuterTest")
// Test 
// Test 
// Test 
// Test 
// OuterTest 
  • 총 시간은 2개의 스레드를 사용하므로 대략 4초가 걸리게 된다.
  • 만약 waitUntilAllOperationsAreFinished()가 없으면 OuterTest의 위치가 항상 마지막에 온다는 것을 보장할 수 없다.
  • 결국 이 메소드가 필요하다면, 차단 메소드를 안전하게 호출할 수 있는 private DispatchQueue를 설정해야 한다.
  • 만약 operation 작업하나만 멈추거나, operation의 배열을 받는다면, OperationQueue의 addOperations(_: waitUntilFinished: ) 메소드를 사용할 수 있다.

Quality of Service

  • OperationQueue는 서로 다른 QoS이 있는 작업을 추가하면 해당 우선 순위에 따라 실행되는 DispatchGroup과 비슷하게 작동한다.
  • OperationQueue의 기본 QoS는 .background이다. OperationQueue의 QoS 속성을 직접 설정할 수 있지만, queue가 관리하는 개별 작업에 설정한 QoS에 의해 재정의될 수 있다.

Pausing the queue

  • isSuspended 속성을 true로 설정하여 OperationQueue를 일시 중지할 수 있다. 실행 중인 작업은 계속 실행되지만, 새로 추가된 작업은 isSuspended를 다시 false로 변경하기 전까지 예약되지 않는다.

Maximum number of operations

  • 기본적으로 디스패치 큐는 장치가 한 번에 처리할 수 있는 최대 작업 수를 실행한다.
  • 작업 수를 제한하려면 디스패치 큐의 maxConcurrentOperationCount 속성을 설정하면 된다.
  • maxConcurrentOperationCount를 1로 설정하면 실질적으로 serial queue를 생성한 것이다.
  • 기본 값은 '-1'이며 이는 운영체제가 알아서 설정하는 것이다.

Underlying DispatchQueue

  • OperationQueue에 작업을 추가히기 전에 기존 DispatchQueue를 기반이되는 queue로 지정할 수 있다. 이렇게 하면 디스패치 큐의 QoS이 작업 큐의 QoS에 설정한 값을 무시한다.
  • 기반이 되는 큐로 메인 큐를 지정하면 안된다.
let queue = OperationQueue()

queue.underlyingQueue = .global()

결론

주요 기능설명
작업 추가OperationQueue에 Operation 객체를 추가하거나, 클로저 또는 Operation 객체의 배열을 전달하여 작업을 추가할 수 있다.
동시성 제어maxConcurrentOperationCount 속성을 사용하여 동시에 실행할 수 있는 작업의 최대 수를 설정할 수 있다. 이를 통해서 동시성을 제어하고, 필요한 경우 순차적으로 작업을 실행할 수 있다.
작업 의존성작업 간의 의존성을 추가하여 특정 작업이 다른 작업이 완료된 후에만 실행되도록 할 수 있다, 이를 통해 복잡한 작업 흐름을 관리할 수 있다.
작업 취소OperationQueue의 cancelAllOperations() 메소드를 사용하여 큐에 있는 모든 작업을 취소할 수 있다. 또한 개별 작업의 cancel() 메소드를 호출하여 특정 작업을 취소할 수도 있다.
작업 대기 및 완료waitUntilAllOperationsAreFinished() 메소드를 사용하여 큐의 모든 작업이 완료될 때까지 현재 스레드를 차단할 수 있다. 그러나 이 메소드는 메인 UI 스레드에서 호출해서는 안 되며, 대신 직렬 DispatchQueue에서 호출해야 한다.

Fix the previous project

  • Concurrency 8 - Operations 참고
  • 기존 임의로 start() 메서드를 사용 한 것 보다 OperationQueue를 활용하면 비동기적으로 작업을 실행할 수 있다.
class TiltShiftTableViewController: UITableViewController { 
	private let queue = OperationQueue()
	
	private let context = CIContext()
	
	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
		return 10
	}
	
	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
		let cell = tableView.dequeueReusableCell(withIdentifier: "normal", for: indexPath) as! PhotoCell
		
		let image = UIImage(named: "\(indexPath.row).png")!
		
		let op = TiltShiftOperation(image: image)
		op.completionBlock = { 
			DispatchQueue.main.async { 
				guard let cell = tableView.cellForRow(at: indexPath) as? PhotoCell else { return }
				
				cell.isLoading = false
				cell.display(image: op.outputImage)
			}
		}
		queue.addOperation(op)
	}
}

출처(참고문헌)

제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.

감사합니다.

0개의 댓글