본문 바로가기
Xcode/Swift - PlayGround

PlayGround) Operation Queue - 2

by 후르륵짭짭 2022. 1. 17.
728x90
반응형

 

안녕하세요! 후르륵짭짭 입니다.

Operation Queue 두번째를 이어가도록 하겠습니다.

내용을 많이 부족하지만,,, 시간이 없어서 죄송합니다.

정말 중요한 내용이 많은데, 깊게 다루지 못 했습니다.

 

** 블록 오퍼레이션은 서로 다른 쓰레드에서 문제가 발생해요! ** 

//기본적으로 의존성을 추가했기 때문에
//Operation2는 Operation1이 끝나고 수행이 된다.
func someTaskone(){
    for i in 0...10{
        print("someTaskOne :\(i) Thread : \(Thread.isMainThread)")
    }
}

func someTaskTwo(){
    for i in 0...10{
        print("someTaskTwo :\(i) Thread : \(Thread.isMainThread)")
    }
}

let operationQueue = OperationQueue()
let operation1 = BlockOperation(block: someTaskone)
let operation2 = BlockOperation(block: someTaskTwo)
operation2.addDependency(operation1)
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)

 

위에 처럼 하면 큰 문제가 없지만 

아래 처럼 할 경우 서로 다른 쓰레드에서 동작해서 문제가 발생합니다.

//위에와 같이 의존성을 추가 했지만
//다음 처럼 다른 쓰레드에서 작업하게 되면
//해당 의존성을 넣었지만, 이미 이 작업은 끝났다고 생각하게 되어
//비동기적으로 서로 작업하게 된다....
func someTaskone(){
    let queue = DispatchQueue(label: "taskone" , attributes: .concurrent)
    queue.async {
        print("someTaskone Thread : \(Thread.current)")
        for i in 0...10{
            print("☺️ :\(i) Thread : \(Thread.isMainThread)")
        }
    }
}

func someTaskTwo(){
    let queue = DispatchQueue(label: "tasktwo" , attributes: .concurrent)
    queue.async {
        print("someTaskTwo Thread : \(Thread.current)")
        for i in 0...10{
            print("🥶 :\(i) Thread : \(Thread.isMainThread)")
        }
    }
}

let operationQueue = OperationQueue()
let operation1 = BlockOperation(block: someTaskone)
let operation2 = BlockOperation(block: someTaskTwo)
operation2.addDependency(operation1)
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)

 

** Opeation과 KVO 방식을 사용해서 이 문제를 해결하자!! ** 

class AsyncOperation : Operation {
    enum State : String {
        case ready
        case executing
        case finished

        //KVO 방식에서 사용될 keyPath로 key 값이 된다.
        var keyPath : String {
            "is\(rawValue.capitalized)"
        }
    }

    var state = State.ready {
        willSet{
            //KVO 방식으로 해당 값이 keyPath로 공유하게 된다.
            willChangeValue(forKey: state.keyPath) //현재 값이 변경 될 예정
            willChangeValue(forKey: newValue.keyPath)//새로운 값도 변경 될 예정
        }
        didSet{
            didChangeValue(forKey: oldValue.keyPath) //이전 값이 변경 되었음
            didChangeValue(forKey: state.keyPath) //현재 값이 변경 되었음
        }
    }

    override var isFinished: Bool {
        print("isFinished : \(self.state)")
        return state == .finished
    } //Opeation Queue에 작업이 들어가기 위해서는 isFinished가 true로 되어 있어야한다.
    // 매 수행 마다 해당 값을 계속 체크한다.

    override var isExecuting: Bool {
        print("isExecuting : \(self.state) ")
        return state == .executing
    }// 매 수행 마다 해당 값을 계속 체크한다.

    override var isAsynchronous: Bool{
        return true
    }// 매 수행 마다 해당 값을 계속 체크한다.

    //비동기일 경우 : Your custom implementation must not call super at any time.
    override func start() {
        if isCancelled {
            state = .finished
            return
        }

        state = .executing
        main()
    }

    override func cancel() {
        print("cancel : \(self.state)")
        state = .finished
    }
}

class SomeTaskOneAsyncOperation : AsyncOperation {
    override func main() {
        let queue = DispatchQueue(label: "taskone" , attributes: .concurrent)
        queue.async {
            print("SomeTaskOneAsyncOperation Thread : \(Thread.current)")
            for i in 0...50{
                print("☺️ :\(i) Thread : \(Thread.isMainThread)")
            }
            self.state = .finished
        }
    }
}

class SomeTaskTwoAsyncOperation : AsyncOperation {
    override func main() {
        let queue = DispatchQueue(label: "tasktwo" , attributes: .concurrent)
        queue.async {
            print("SomeTaskTwoAsyncOperation Thread : \(Thread.current)")
            for i in 0...50{
                print("🥶 :\(i) Thread : \(Thread.isMainThread)")
            }
            self.state = .finished
        }
    }
}

위에 처럼 비동기Operation을 생성한다.

위 Operation은 KVO 방식으로 작동하고 

Operation은 해당 작업이 Queue에서 작업해야할 건지 아닌지 

isReady , isExecuting, isFinished, isCanceled를 통해서 판단하게 된다.

그런데 read Only이기 때문에 위에 처럼 우리가 직업 overrid 받아서 사용해줘야한다.

//비동기적 Opeartion Queue를 생성하는 방법
let operationQueue = OperationQueue()
let operation1 = SomeTaskOneAsyncOperation()
let operation2 = SomeTaskTwoAsyncOperation()
operation1.completionBlock = {print("SomeTaskOneAsyncOperation completed")}
operation2.completionBlock = {print("SomeTaskTwoAsyncOperation completed")}
operation2.addDependency(operation1) //KVO 방식을 사용한 상태에서 의존성을 추가하면 같은 Thread 주소를 가지게 된다.
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)

 

** 위의 비동기 Operation을 Thread Safe 하게 만드는 방법 **

//의존성 없이 비동기적 작업을 할때 Thread Safe하게 할 수 있는 방법
class AsyncOperation2 : Operation {
    enum State : String {
        case ready
        case executing
        case finished

        var keyPath : String {
            "is\(rawValue.capitalized)"
        }
    }

    private let stateQueue = DispatchQueue(label: "custon", attributes: .concurrent)
    private var _state = State.ready

    var state : State {
        get{
            stateQueue.sync {
                return _state
            }
        }
        set{
            let oldValue = state
            willChangeValue(forKey: state.keyPath)
            willChangeValue(forKey: newValue.keyPath)
            stateQueue.sync(flags: .barrier) {
                _state = newValue
            }
            didChangeValue(forKey: state.keyPath)
            didChangeValue(forKey: oldValue.keyPath)
        }
    }

    override var isFinished: Bool {
        print("isFinished : \(self.state)")
        return state == .finished
    } //Opeation Queue에 작업이 들어가기 위해서는 isFinished가 true로 되어 있어야한다.

    override var isExecuting: Bool {
        print("isExecuting : \(self.state) ")
        return state == .executing
    }

    override var isAsynchronous: Bool{
//        print("isAsynchronous : \(self.state)")
        return true
    }

    //비동기일 경우 : Your custom implementation must not call super at any time.
    override func start() {
        if isCancelled {
            state = .finished
            return
        }

        state = .executing
        main()
    }

    override func cancel() {
        print("cancel : \(self.state)")
        state = .finished
    }
}

class SomeTaskOneAsyncOperation2 : AsyncOperation {
    override func main() {
        let queue = DispatchQueue(label: "taskone" , attributes: .concurrent)
        queue.async {
            print("SomeTaskOneAsyncOperation2 Thread : \(Thread.current)")
            for i in 0...50{
                print("☺️ :\(i) Thread : \(Thread.isMainThread)")
            }
            self.state = .finished
        }
    }
}

class SomeTaskTwoAsyncOperation2 : AsyncOperation {
    override func main() {
        let queue = DispatchQueue(label: "tasktwo" , attributes: .concurrent)
        queue.async {
            print("SomeTaskTwoAsyncOperation2 Thread : \(Thread.current)")
            for i in 0...50{
                print("🥶 :\(i) Thread : \(Thread.isMainThread)")
            }
            self.state = .finished
        }
    }
}

추후 다룰내용이지만 

큐를 만들고 해당 값을 수정할 때만 barrier를 만들어서 

Thread Safe하게 만든다.

let operationQueue = OperationQueue()
let operation1 = SomeTaskOneAsyncOperation2()
let operation2 = SomeTaskTwoAsyncOperation2()
operation1.completionBlock = {print("SomeTaskOneAsyncOperation2 completed")}
operation2.completionBlock = {print("SomeTaskTwoAsyncOperation2 completed")}
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)

 

참고 사이트 : 

https://ali-akhtar.medium.com/concurrency-in-swift-custom-operations-part-4-154b60bff84c

 

Concurrency in Swift (Custom Operations Part 4)

As shown in Figure 1 we created two operations and we added both in operation queue, In addition to this, we added a dependency rule that…

ali-akhtar.medium.com

https://blog.bitbebop.com/asynchronous-operations-swift/

 

Asynchronous Operations in Swift

This is the first part of two dealing with how to handle preloading of SpriteKit game assets in Swift using an OperationQueue with asynchronous Operation object...

blog.bitbebop.com

https://ali-akhtar.medium.com/concurrency-in-swift-grand-central-dispatch-part-1-945ff05e8863

 

Concurrency in Swift (Grand Central Dispatch Part 1)

In this part we will cover following topics

ali-akhtar.medium.com

https://ali-akhtar.medium.com/concurrency-in-swift-grand-central-dispatch-part-2-1b0b025ee381

 

Concurrency in Swift (Grand Central Dispatch Part 2)

Dispatch Group

ali-akhtar.medium.com

 

728x90
반응형

댓글