【问题标题】:Swift Code Running Twice When It Should NotSwift 代码在不应该运行的情况下运行两次
【发布时间】:2020-11-11 18:05:11
【问题描述】:

我有一个转义函数,一旦满足条件就完成:

  private func xxxfastLoadLSecurityDescriptions(session: URLSession, mySymbols: [String]?, completion: @escaping(Bool) ->())    {
        
        var counter = mySymbols?.count ?? 0
        if counter == 0 { completion(false) }
        var doubleCount = 0
        // print("DESCRIPTION Starting Counter = \(counter)")
        
        for symbolIndex in 0..<(mySymbols?.count ?? 0) {
            guard let mySymbol = mySymbols?[symbolIndex] else { print("ERROR in fastLoadLSecurityDescriptions loop: No Symbol") ;  continue }
            guard let myGetDescriptionRequest = GenericDataRequest(dataToSend: [mySymbol], token: sessionToken, mgbRoute: MGBServerRoutes.retrieveSecurityDescriptionsRoute!)
            else { print("Error Getting Security Description Request for \(mySymbol)") ; return }
            
            mySessionSendMGBGenericRequest(session: session, request: myGetDescriptionRequest) { [weak self]  success, serverMessage, returnUNISecurityDescription in
                guard let self = self else { print("ERROR: self is nil") ; return }
                if returnUNISecurityDescription?.count == 0 { print("nil returnUniSecurityDescription for \(mySymbol)") }
                // print("DESCRIPTIONS COUNTER = \(counter)")
                counter -= 1
                var myDescription = UNISecurityDescription()
                if returnUNISecurityDescription != nil, returnUNISecurityDescription?.count != 0 { myDescription = returnUNISecurityDescription![0]  }
                if myDescription.name == nil || myDescription.name == "" { print("Error: No Name for \(String(describing: mySymbol))") }
                let myContainersIndices = self.myUNIList.singleContainer.indices.filter({ self.myUNIList.singleContainer[$0].ticker?.symbol == mySymbol })
                var myPathArray = [IndexPath]()
                for index in 0..<myContainersIndices.count {
                    self.myUNIList.singleContainer[myContainersIndices[index]].name = myDescription.name
                    self.myUNIList.singleContainer[myContainersIndices[index]].currencySymbol = myDescription.currencySymbol
                    self.myUNIList.singleContainer[myContainersIndices[index]].fillFundamentals() // --> Fills the outputs for sortdata
                    myPathArray.append(IndexPath(row: myContainersIndices[index], section: 0))
                }
                DispatchQueue.main.async {
                    self.filteredData = self.myUNIList.singleContainer
                    self.myCollection?.reloadItems(at: myPathArray)
                }
                if counter == 0     {   // THIS IS TRUE MORE THAN ONCE WHILE IT SHOULD NOT BE TRU MORE THAN ONCE
                    if doubleCount > 0 {    print("WHY!!!!")  }
                    doubleCount += 1
                    print("DESCRIPTIONS counter = \(counter) -> \(self.myUNIList.listName) - symbols: \(String(describing: mySymbols?.count)) \n==================================================\n")
                    DispatchQueue.main.async {   self.sortNormalTap("Load")  { _ in     self.displayAfterLoading()  } }
                    completion(true)
                    return
                }
            }
        }
    }

要满足的条件是 counter == 0。一旦满足,函数完成并退出 DispatchGroup。问题是 counter == 0 多次为真(退出 DispatchGroup 时明显崩溃)。我真的不明白为什么不止一次满足这个条件。代码非常线性,我看不出是什么原因造成的。非常感谢任何帮助。这让我快疯了。

【问题讨论】:

  • mySessionSendMGBGenericRequest 不在主线程上执行,因此当您更改其块内的计数器时,其他线程不会知道它。您可以将counter 设为静态,因此每个块都更新相同的值,这不是一个好方法,因为您确实应该在某种回调或委托方法中增加计数器,但它应该可以工作。 static var counter = 0 并使用 Self.counter 访问该值。可能也应该在 async 调用中减少它。
  • @clawesome:计数器工作正常。它在块内必须是唯一的。每次从 mySessionSendMGBGenericRequest 返回时,它都会递减,并且它确实线性地变为 0。这部分有效。不起作用的是 if counter == 0 两次(或更多)被认为是 true,所以我想知道是什么让那里的执行不止一次。
  • 当计数器为 0 时,您似乎在完成块之后缺少返回。 if 语句之后的代码仍在执行。我想这不是你想要的????‍♂️
  • 您是否可能在循环中调用xxxfastLoadLSecurityDescriptions?此外,您是否期望 completion(true) 和之后的 return 打破 mySymbols?.count 上的 for 循环?

标签: swift asynchronous dispatchgroup


【解决方案1】:

您的代码不是线程安全的,尤其是计数器。我使用相同的逻辑编写了一个示例来说明这一点。如果你运行它几次,你最终会遇到问题发生的相同情况。

override func viewDidLoad() {
    super.viewDidLoad()

    let mySymbols: [Int] = Array(0...100)

    for _ in 0..<100 {
        xxxfastLoadLSecurityDescriptions(session: URLSession.shared, mySymbols: mySymbols) { (success, counter, doubleCount) in
            print("Completed: \(success), Counter: \(counter), Double Count: \(doubleCount)")
        }
    }
}

private func xxxfastLoadLSecurityDescriptions(session: URLSession, mySymbols: [Int]?, completion: @escaping(Bool, Int, Int) ->())    {

    var counter = mySymbols?.count ?? 0

    if counter == 0 {
        return completion(false, -1, -1)
    }
    var doubleCount = 0

    for symbolIndex in 0..<(mySymbols?.count ?? 0) {
        guard let _ = mySymbols?[symbolIndex] else {
            print("Error")
            continue
        }

        DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(Int.random(in: 50..<900))) {

            counter -= 1

            DispatchQueue.main.async {
                self.view.layoutIfNeeded()
            }

            if counter == 0 {
                if doubleCount > 0 {
                    // This will eventually print even though logically it shouldn't
                    print("*****Counter: \(counter), Double Count: \(doubleCount), Symbol Index: \(symbolIndex)")
                }
                doubleCount += 1
                completion(true, counter, doubleCount)
                return
            }
        }
    }
}

输出:

Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
*******************************************************************
*****   Counter: 0, Double Count: 1, Symbol Index: 15   
*******************************************************************
Completed: true, Counter: 0, Double Count: 2
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
*******************************************************************
*****   Counter: 0, Double Count: 1, Symbol Index: 26   
*******************************************************************
Completed: true, Counter: 0, Double Count: 2
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
*******************************************************************
*****   Counter: 0, Double Count: 1, Symbol Index: 57   
*******************************************************************
Completed: true, Counter: 0, Double Count: 2
*******************************************************************
*****   Counter: 0, Double Count: 1, Symbol Index: 3    
*******************************************************************
Completed: true, Counter: 0, Double Count: 2

【讨论】:

  • @goergisn:完成块后没有代码。添加退货不会改变任何事情
  • 我看到了你的评论。但是我不理解。我看到了很多关于如何计算来自 HTTP 调用循环的所有必需响应何时以我实现的逻辑返回的示例。你会怎么做?
  • 该块不应该负责减少计数器,因为它超出了它的范围。在块减少计数器的时间和它检查它是否为 0 的时间之间,其他线程之一也可能已经减少了它。您应该有一个拥有计数器的调用者,并且该调用者应该在回调完成时递减计数器。你能链接你提到的例子之一吗?
  • 我想我现在明白了。不确定如何实现一种线程安全的方法来保持计数。您的回复是有道理的(抱歉,我不是异步专家)。非常感谢任何有关如何执行此操作的帮助。
【解决方案2】:

最后我使用 DispatchGroup 解决了这个问题,如下(简化代码):

private func xxxFastLoadLSecurityDescriptions(session: URLSession, mySymbols: [String]?, completion: @escaping(Bool) ->())    {
    
    let myGroup = DispatchGroup()
    
    for symbolIndex in 0..<(mySymbols?.count ?? 0) {
        
        myGroup.enter()
    
        mySessionSendMGBGenericRequest(...) { [weak self] returnValue in
            
            guard let self = self else { myGroup.leave() ; return }
         // do some stuff with returnValue
            
            myGroup.leave()
        }
    }
    
    myGroup.notify(queue: .main, execute: {
        
        completion(true)
    })
}

我的问题是:我是否可能遇到与以前相同的问题?例如,假设我有 2 个项目的循环。第一项进入组并说在第二项进入循环之前异步调用返回一个值并且第一项退出组。此时,在第二项进入组之前,应该触发.notify,并且completion(true)在处理第二项之前存在该函数。这可能吗?

【讨论】:

    猜你喜欢
    • 2017-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多