简体   繁体   English

Swift:从url下载数据会导致semaphore_wait_trap冻结

[英]Swift: downloading data from url causes semaphore_wait_trap freeze

In my application, button tapping downloads data from an Internet site. 在我的应用程序中,按钮点击从Internet站点下载数据。 The site is a list of links containing binary data. 该站点是包含二进制数据的链接列表。 Sometimes, the first link may not contain the proper data. 有时,第一个链接可能不包含正确的数据。 In this case, the application takes the next link in the array and gets data from there. 在这种情况下,应用程序获取数组中的下一个链接并从那里获取数据。 The links are correct. 链接是正确的。

The problem I have is that frequently (not always though) the application freezes for seconds when I tap on the button. 我遇到的问题是,当我点击按钮时,应用程序经常(并非总是如此)冻结几秒钟。 After 5-30 seconds, it unfreezes and downloading implements normally. 5-30秒后,它会解冻并正常下载工具。 I understand, something is blocking the main thread. 我理解,有些东西阻塞了主线程。 When stopping the process in xCode, I get this (semaphore_wait_trap noted): 在xCode中停止进程时,我得到了这个(semaphore_wait_trap注明):

在此输入图像描述

This is how I do it: 我是这样做的:

// Button Action
@IBAction func downloadWindNoaa(_ sender: UIButton)
    {
            // Starts activity indicator
            startActivityIndicator()

            // Starts downloading and processing data

            // Either use this
            DispatchQueue.global(qos: .default).async
                {
                    DispatchQueue.main.async
                        {
                            self.downloadWindsAloftData()
                        }
                }


            // Or this - no difference.
            //downloadWindsAloftData()
        }
    }

func downloadWindsAloftData()
    {
        // Creates a list of website addresses to request data: CHECKED.
        self.listOfLinks = makeGribWebAddress()

        // Extract and save the data
        saveGribFile()
    }

// This downloads the data and saves it in a required format. I suspect, this is the culprit

    func saveGribFile()
    {
        // Check if the links have been created
        if (!self.listOfLinks.isEmpty)
        {
            /// Instance of OperationQueue
            queue = OperationQueue()

            // Convert array of Strings to array of URL links
            let urls = self.listOfLinks.map { URL(string: $0)! }

            guard self.urlIndex != urls.count else
            {
                NSLog("report failure")
                return
            }

            // Current link
            let url = urls[self.urlIndex]

            // Increment the url index
            self.urlIndex += 1

            // Add operation to the queue
            queue.addOperation { () -> Void in

                // Variables for Request, Queue, and Error
                let request = URLRequest(url: url)
                let session = URLSession.shared

                // Array of bytes that will hold the data
                var dataReceived = [UInt8]()

                // Read data
                let task = session.dataTask(with: request) {(data, response, error) -> Void in

                    if error != nil
                    {
                        print("Request transport error")
                    }
                    else
                    {
                        let response = response as! HTTPURLResponse
                        let data = data!

                        if response.statusCode == 200
                        {
                            //Converting data to String
                            dataReceived = [UInt8](data)
                        }
                        else
                        {
                            print("Request server-side error")
                        }
                    }

                    // Main thread
                    OperationQueue.main.addOperation(
                        {
                            // If downloaded data is less than 2 KB in size, repeat the operation
                            if dataReceived.count <= 2000
                            {
                                self.saveGribFile()
                            }

                            else
                            {
                                self.setWindsAloftDataFromGrib(gribData: dataReceived)

                                // Reset the URL Index back to 0
                                self.urlIndex = 0
                            }
                        }
                    )
                }
                task.resume()
            }
        }
    }


// Processing data further
func setWindsAloftDataFromGrib(gribData: [UInt8])
    {
        // Stops spinning activity indicator
        stopActivityIndicator()

        // Other code to process data...
    }

// Makes Web Address

let GRIB_URL = "http://xxxxxxxxxx"

func makeGribWebAddress() -> [String]
    {
        var finalResult = [String]()

        // Main address site
        let address1 = "http://xxxxxxxx"

        // Address part with type of data
        let address2 = "file=gfs.t";
        let address4 = "z.pgrb2.1p00.anl&lev_250_mb=on&lev_450_mb=on&lev_700_mb=on&var_TMP=on&var_UGRD=on&var_VGRD=on"

        let leftlon = "0"
        let rightlon = "359"
        let toplat = "90"
        let bottomlat = "-90"

        // Address part with coordinates
        let address5 = "&leftlon="+leftlon+"&rightlon="+rightlon+"&toplat="+toplat+"&bottomlat="+bottomlat

        // Vector that includes all Grib files available for download
        let listOfFiles = readWebToString()

        if (!listOfFiles.isEmpty)
        {
            for i in 0..<listOfFiles.count
            {
                // Part of the link that includes the file
                let address6 = "&dir=%2F"+listOfFiles[i]

                // Extract time: last 2 characters
                let address3 = listOfFiles[i].substring(from:listOfFiles[i].index(listOfFiles[i].endIndex, offsetBy: -2))

                // Make the link
                let addressFull = (address1 + address2 + address3 + address4 + address5 + address6).trimmingCharacters(in: .whitespacesAndNewlines)

                finalResult.append(addressFull)
            }
        }

        return finalResult;
    }


func readWebToString() -> [String]
    {
        // Final array to return
        var finalResult = [String]()

        guard let dataURL = NSURL(string: self.GRIB_URL)
            else
        {
            print("IGAGribReader error: No URL identified")
            return []
        }

        do
        {
            // Get contents of the page
            let contents = try String(contentsOf: dataURL as URL)

            // Regular expression
            let expression : String = ">gfs\\.\\d+<"
            let range = NSRange(location: 0, length: contents.characters.count)

            do
            {
                // Match the URL content with regex expression
                let regex = try NSRegularExpression(pattern: expression, options: NSRegularExpression.Options.caseInsensitive)
                let contentsNS = contents as NSString
                let matches = regex.matches(in: contents, options: [], range: range)

                for match in matches
                {
                    for i in 0..<match.numberOfRanges
                    {
                        let resultingNS = contentsNS.substring(with: (match.rangeAt(i))) as String
                        finalResult.append(resultingNS)
                    }
                }

                // Remove "<" and ">" from the strings
                if (!finalResult.isEmpty)
                {
                    for i in 0..<finalResult.count
                    {
                        finalResult[i].remove(at: finalResult[i].startIndex)
                        finalResult[i].remove(at: finalResult[i].index(before: finalResult[i].endIndex))
                    }
                }
            }
            catch
            {
                print("IGAGribReader error: No regex match")
            }

        }
        catch
        {
            print("IGAGribReader error: URL content is not read")
        }


        return finalResult;
    }

I have been trying to fix it for the past several weeks but in vain. 过去几周我一直试图修复它,但是徒劳无功。 Any help would be much appreciated! 任何帮助将非常感激!

在此输入图像描述

        let contents = try String(contentsOf: dataURL as URL)

You are calling String(contentsOf: url) on the main thread (main queue). 您正在主线程(主队列)上调用String(contentsOf: url) )。 This downloads the content of the URL into a string synchronously The main thread is used to drive the UI, running synchronous network code is going to freeze the UI. 这会将URL的内容同步下载到字符串中。主线程用于驱动UI,运行同步网络代码将冻结UI。 This is a big no-no . 这是一个很大的禁忌

You should never call readWebToString() in the main queue. 你永远不应该在主队列中调用readWebToString() Doing DispatchQueue.main.async { self.downloadWindsAloftData() } exactly put the block in the main queue which we should avoid. 执行DispatchQueue.main.async { self.downloadWindsAloftData() }确切地将块放在我们应该避免的主队列中。 ( async just means "execute this later", it is still executed on Dispatch.main .) async只是意味着“稍后执行”,它仍然在Dispatch.main执行。)

You should just run downloadWindsAloftData in the global queue instead of main queue 您应该在全局队列而不是主队列中运行downloadWindsAloftData

    DispatchQueue.global(qos: .default).async {
        self.downloadWindsAloftData()
    }

Only run DispatchQueue.main.async when you want to update the UI. 只有在想要更新UI时才运行DispatchQueue.main.async

Your stack trace is telling you that it's stopping at String(contentsOf:) , called by readWebToString , called by makeGribWebAddress . 您的堆栈跟踪告诉您它停止在String(contentsOf:) ,由readWebToString调用,由makeGribWebAddress

The problem is that String(contentsOf:) performs a synchronous network request. 问题是String(contentsOf:)执行同步网络请求。 If that request takes any time, it will block that thread. 如果该请求需要任何时间,它将阻止该线程。 And if you call this from the main thread, your app may freeze. 如果你从主线程中调用它,你的应用程序可能会冻结。

Theoretically, you could just dispatch that process to a background queue, but that merely hides the deeper problem, that you are doing a network request with an API that is synchronous, non-cancellable, and offers no meaningful error reporting. 从理论上讲,您可以将该进程调度到后台队列,但这只是隐藏了更深层次的问题,即您正在使用同步,不可取消的API执行网络请求,并且不提供有意义的错误报告。

You really should doing asynchronous requests with URLSession , like you have elsewhere. 您真的应该使用URLSession进行异步请求,就像您在其他地方一样。 Avoid using String(contentsOf:) with remote URL. 避免将String(contentsOf:)与远程URL一起使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM