How to check if operation was cancelled?

491 Views Asked by At

I'd like to know whether my operation was cancelled or finished normally. Here is my code snippet :

class PacketReceiver {
    private let m_executer = OperationQueue()
    private var m_operation : Operation?

    private func operationFinishedHandler()
    {
        if let op = m_operation {
            if op.isCancelled {
                print("Operation cancelled")
            } else {
                print("Operation finished OK \(op.isCancelled)")
            }
        }
    }

    func start()
    {
        let rxPacketOperation = PacketReceiverOperation()
        rxPacketOperation.completionBlock = self.operationFinishedHandler
        m_operation = rxPacketOperation

        m_executer.addOperation(rxPacketOperation)
    }

    func cancel()
    {
        if let op = m_operation {
            op.cancel()
        }
    }

    func join()
    {
        if let op = m_operation {
            print("is op cancelled = \(op.isCancelled)")
        }

        m_executer.waitUntilAllOperationsAreFinished()

        if let op = m_operation {
            print("is op cancelled = \(op.isCancelled)")
        }
    }
}

And later on I have :

let packetReceiver = PacketReceiver()
packetReceiver.start()

packetReceiver.cancel()
packetReceiver.join()

I see that inside join() method isCancelled property is set to true. But when I check it inside finished handler it is false. I guess it's an expected behaviour. But what is the correct way to check whether my operation was cancelled or not?

This is my operation class :

class PacketReceiverOperation : Operation {
    private var m_packetData : String = ""
    private let m_simDataProvider = SimDataProvider(okAttemptId : 2)
    private let m_currentTimeProvider = CurrentTimeProvider()

    private func prepareReceiver() {
        m_packetData = ""
        print("\(m_currentTimeProvider.getCurrentTimeAsString()) : prepare SIM receiver")
    }

    override func main() {
        if isCancelled {
            return
        }

        prepareReceiver()

        if isCancelled {
            return
        }

        var isPacketReceived = false
        var isFirstChunkReceived = false

        repeat {
            if isCancelled {
                return
            }

            if let chunkData = m_simDataProvider.getPacket() {
                isFirstChunkReceived = true
                m_packetData.append(";" + chunkData)
            } else {
                if isFirstChunkReceived {
                    isPacketReceived = true
                }
            }
        } while isPacketReceived == false
    }
}
1

There are 1 best solutions below

3
Rob On

For a variety of reasons (notably the lack of preemptive cancelation and general race conditions) the isCancelled state is not a reliable way of knowing whether your Operation subclass finished successfully or not, regardless. (It tells you whether you attempted cancelation, but not whether the underlying task succeeded or not.)

For this reason, I rarely use the built in completionHandler (with no parameters) and instead generally implement my own custom completion handler closure that supplies a Result (or whatever) as parameter. This eliminates all ambiguity.

Or, in your case, you could make isPacketReceived a property of your operation, instead of a local variable, and then you could look at that to determine whether a packet was completely received or not rather than isCancelled.

You later said:

I see that completionHandler is invoked after join() finishes.

Yes, that’s true.

But it’s a bit worrisome that join is a synchronous method, at all, (i.e. that you’re calling waitUntilAllOperationsAreFinished). The intent of operation queues is so we can adopt asynchronous patterns for potentially time consuming tasks and get this stuff off the main thread. Blocking while we wait for an operation to finish (especially if you’re calling join from the main thread) would seem to defeat that purpose.

I’d encourage you to embrace the asynchronous pattern and just add your operation to a queue and initiate whatever you need in its completion handler (whether the default one or your own).