同步进行多个地图本地搜索

Making multiple map local searches synchronously

我尝试同步进行多个搜索(我的意思是一个接一个地等待前一个请求完成,然后再 运行 下一个请求)并阻塞直到所有操作完成,然后再继续。

但是本地搜索的完成句柄看起来像阻塞了,运行 一旦信号量放弃。我试了很多次都没有成功。

我的代码和日志如下(可以copy/paste去操场):

import CoreLocation
import MapKit


func search(_ query: String, in span: MKCoordinateSpan, centered center: CLLocationCoordinate2D, id: Int) {

    let semaphore = DispatchSemaphore(value: 0)
    //let group = DispatchGroup(); group.enter()

    // Run the request for this rect
    print("\(#function): local search on the \(id)th portion ")

    let request = MKLocalSearch.Request()
    request.naturalLanguageQuery = query
    request.region = MKCoordinateRegion(center: center, span: span)

    if #available(iOS 13, *) {
        request.resultTypes = .pointOfInterest
    }


    let search = MKLocalSearch(request: request)

    search.start { response, error in
        print("\(id) got \(response?.mapItems.count) items")
        semaphore.signal()
    }


    let s = semaphore
    //let s = group

    // Wait for the request ot complete
    print("\(#function): waiting for the \(id)th portion to complete")
    //guard _ = s.wait(wallTimeout: .distantFuture) else {
    guard s.wait(timeout: .now() + 5) == .success else {
        print("\(#function): ***Warning: \(id)th timeout, job incomplete")
        return
    }

    print("\(#function): \(id)th completed")
}



let rect = CGRect(
    x: 48.10,
    y: 3.43,
    width: 0.09,
    height: 0.09
)


let n = 4
let latDelta = rect.width / CGFloat(n)
var latOffs = rect.minX



let queue = OperationQueue()
//queue.maxConcurrentOperationCount = 1
var ops = [BlockOperation]()

// -- Run all asyn loca search requests synchronuously
for i in 0..<n {
    // Take the next cut of the original region
    let portion = CGRect(
        x: latOffs,
        y: rect.minY,
        width: latDelta,
        height: rect.height
    )

    latOffs += latDelta

    ops.append(BlockOperation { [portion, i] in
        let center = CLLocationCoordinate2D(latitude: CLLocationDegrees(portion.midX), longitude: CLLocationDegrees(portion.midY))
        let span = MKCoordinateSpan(latitudeDelta: CLLocationDegrees(portion.width), longitudeDelta: CLLocationDegrees(portion.height))

        search("coffee", in: span, centered: center, id: i)
    })
}

queue.addOperations(ops, waitUntilFinished: true)

print("All done")

当前伪输出:

search(_:in:centered:id:): local search on the 1th portion 
search(_:in:centered:id:): local search on the 2th portion 
search(_:in:centered:id:): local search on the 3th portion 
search(_:in:centered:id:): local search on the 0th portion 
search(_:in:centered:id:): waiting for the 1th portion to complete
search(_:in:centered:id:): waiting for the 3th portion to complete
search(_:in:centered:id:): waiting for the 2th portion to complete
search(_:in:centered:id:): waiting for the 0th portion to complete
search(_:in:centered:id:): ***Warning: 0th timeout, job incomplete
search(_:in:centered:id:): ***Warning: 2th timeout, job incomplete
search(_:in:centered:id:): ***Warning: 1th timeout, job incomplete
search(_:in:centered:id:): ***Warning: 3th timeout, job incomplete
All done
0 got Optional(10) items
2 got Optional(10) items
1 got Optional(10) items
3 got Optional(10) items

[更新]

预期的输出应该没有显示 ***WarningAll done 作为最后一行,如下(编号的确切顺序取决于网络条件):

search(_:in:centered:id:): local search on the 1th portion 
search(_:in:centered:id:): local search on the 2th portion 
search(_:in:centered:id:): local search on the 3th portion 
search(_:in:centered:id:): local search on the 0th portion 
search(_:in:centered:id:): waiting for the 1th portion to complete
search(_:in:centered:id:): waiting for the 3th portion to complete
search(_:in:centered:id:): waiting for the 2th portion to complete
search(_:in:centered:id:): waiting for the 0th portion to complete
0 got Optional(10) items
search(_:in:centered:id:): 0th completed
2 got Optional(10) items
search(_:in:centered:id:): 2th completed
1 got Optional(10) items
search(_:in:centered:id:): 1th completed
3 got Optional(10) items
search(_:in:centered:id:): 3th completed
All done

[UPDATE 2] 取消注释行 //queue.maxConcurrentOperationCount = 1

时的输出

search(:in:centered:id:): 本地搜索第0部分 2020-03-28 23:49:41 +0000 search(:in:centered:id:): 等待第 0 部分完成 2020-03-28 23:49:41 +0000 search(:in:centered:id:): ***警告:第 0 次超时,作业未完成 2020-03-28 23:49:46 +0000 search(:in:centered:id:): 本地搜索第 1 部分 2020-03-28 23:49:46 +0000 search(:in:centered:id:): 等待第 1 部分完成 2020-03-28 23:49:46 +0000 search(:in:centered:id:): ***警告:第 1 次超时,作业未完成 2020-03-28 23:49:51 +0000 search(:in:centered:id:): 本地搜索第2部分 2020-03-28 23:49:51 +0000 search(:in:centered:id:): 等待第二部分完成 2020-03-28 23:49:51 +0000 search(:in:centered:id:): ***警告:第二次超时,作业未完成 2020-03-28 23:49:56 +0000 search(:in:centered:id:): 本地搜索第3部分 2020-03-28 23:49:56 +0000 search(:in:centered:id:): 等待第 3 部分完成 2020-03-28 23:49:56 +0000 search(:in:centered:id:): ***警告:第三次超时,作业未完成 2020-03-28 23:50:01 +0000 全部完成 2020-03-28 23:50:01 +0000 0 得到了可选的(10)项 2020-03-28 23:50:02 +0000 3 获得了可选的(10)项 2020-03-28 23:50:02 +0000 2 获得了可选的(10)项 2020-03-28 23:50:02 +0000 1 获得了可选的 (10) 项 2020-03-28 23:50:02 +0000

注意:顺便说一句,我还在每个 print

的末尾添加了 \(Date())

如果您希望这些操作以串行方式运行,则必须指定队列一次只能运行一个,例如

queue.maxConcurrentOperationCount = 1

而且,正如您发现的那样,您希望避免使用 addOperationswaitUntilFinished 选项,因为这会阻塞当前线程,直到操作完成。相反,使用完成处理程序模式。


这是我使用的代码:

func performMultipleSearches(completion: @escaping () -> Void) {
    let searches = ["restaurant", "coffee", "hospital", "natural history museum"]

    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 1

    for (i, searchText) in searches.enumerated() {
        queue.addOperation {
            self.search(searchText, in: self.mapView.region, id: i)
        }
    }

    queue.addOperation {
        completion()
    }
}

func search(_ query: String, in region: MKCoordinateRegion, id: Int) {
    let semaphore = DispatchSemaphore(value: 0)

    os_log("%d starting", id)

    let request = MKLocalSearch.Request()
    request.naturalLanguageQuery = query
    request.region = region

    if #available(iOS 13, *) {
        request.resultTypes = .pointOfInterest
    }

    let search = MKLocalSearch(request: request)

    search.start { response, error in
        defer { semaphore.signal() }

        guard let mapItems = response?.mapItems else {
            os_log("  %d failed", id)
            return
        }

        os_log("  %d succeeded, found %d:", id, mapItems.count)
    }

    os_log("  %d waiting", id)
    guard semaphore.wait(timeout: .now() + 5) == .success else {
        os_log("  %d timedout", id)
        return
    }

    os_log("  %d done", id)
}

产生了:

2020-03-28 16:16:25.219565-0700 MyApp[46601:2107182] 0 starting
2020-03-28 16:16:25.220018-0700 MyApp[46601:2107182]   0 waiting
2020-03-28 16:16:25.438121-0700 MyApp[46601:2107033]   0 succeeded, found 10:
2020-03-28 16:16:25.438269-0700 MyApp[46601:2107182]   0 done
2020-03-28 16:16:25.438436-0700 MyApp[46601:2107182] 1 starting
2020-03-28 16:16:25.438566-0700 MyApp[46601:2107182]   1 waiting
2020-03-28 16:16:25.639198-0700 MyApp[46601:2107033]   1 succeeded, found 10:
2020-03-28 16:16:25.639357-0700 MyApp[46601:2107182]   1 done
2020-03-28 16:16:25.639490-0700 MyApp[46601:2107182] 2 starting
2020-03-28 16:16:25.639598-0700 MyApp[46601:2107182]   2 waiting
2020-03-28 16:16:25.822085-0700 MyApp[46601:2107033]   2 succeeded, found 10:
2020-03-28 16:16:25.822274-0700 MyApp[46601:2107182]   2 done
2020-03-28 16:16:25.822422-0700 MyApp[46601:2107162] 3 starting
2020-03-28 16:16:25.822567-0700 MyApp[46601:2107162]   3 waiting
2020-03-28 16:16:26.015566-0700 MyApp[46601:2107033]   3 succeeded, found 1:
2020-03-28 16:16:26.015696-0700 MyApp[46601:2107162]   3 done
2020-03-28 16:16:26.015840-0700 MyApp[46601:2107162] all done

为了它的价值,我不会使用信号量,而是使用异步 Operation subclass。例如,您可以使用 AsynchronousOperation class ,然后执行:

class SearchOperation: AsynchronousOperation {
    let identifier: Int
    let searchText: String
    let region: MKCoordinateRegion

    init(identifier: Int, searchText: String, region: MKCoordinateRegion) {
        self.identifier = identifier
        self.searchText = searchText
        self.region = region

        super.init()
    }

    override func main() {
        os_log("%d started", identifier)

        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = searchText
        request.region = region

        if #available(iOS 13, *) {
            request.resultTypes = .pointOfInterest
        }

        let search = MKLocalSearch(request: request)

        search.start { response, error in
            defer { self.finish() }

            guard let mapItems = response?.mapItems else {
                os_log("  %d failed", self.identifier)
                return
            }

            os_log("  %d succeeded, found %d:", self.identifier, mapItems.count)
        }
    }
}

然后

let searches = ["restaurant", "coffee", "hospital", "natural history museum"]

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1

for (i, searchText) in searches.enumerated() {
    queue.addOperation(SearchOperation(identifier: i, searchText: searchText, region: mapView.region))
}

queue.addOperation {
    completion()
}