[英]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.