我有 3 個這樣的功能:
func getMyFirstItem(complete: @escaping (Int) -> Void) {
DispatchQueue.main.async {
complete(10)
}
}
func getMySecondtItem(complete: @escaping (Int) -> Void) {
DispatchQueue.global(qos:.background).async {
complete(10)
}
}
func getMyThirdItem(complete: @escaping (Int) -> Void) {
DispatchQueue.main.async {
complete(10)
}
}
我有一個變數:
var myItemsTotal: Int = 0
我想知道如何對專案求和,在這種情況下 10 10 10 得到 30。但是最好的方法是什么,因為是背景和主要。
uj5u.com熱心網友回復:
關鍵問題是確保執行緒安全。例如,以下不是執行緒安全的:
func addUpValuesNotThreadSafe() {
var total = 0
getMyFirstItem { value in
total = value // on main thread
}
getMySecondItem { value in
total = value // on some GCD worker thread!!!
}
getMyThirdItem { value in
total = value // on main thread
}
...
}
可以通過不允許這些任務并行運行來解決這個問題,但是您將失去異步行程和它們提供的并發性的所有好處。
不用說,當您允許它們并行運行時,您可能會添加一些機制(例如調度組)來了解所有這些異步任務何時完成。但我不想讓這個例子復雜化,而是將我們的注意力集中在執行緒安全問題上。(我將在本答案后面展示如何使用調度組。)
無論如何,如果您有從多個執行緒呼叫的閉包,則不能在total不添加一些同步的情況下增加相同的閉包。您可以添加與串行調度佇列的同步,例如:
func addUpValues() {
var total = 0
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! ".synchronized")
getMyFirstItem { value in
queue.async {
total = value // on serial queue
}
}
getMySecondItem { value in
queue.async {
total = value // on serial queue
}
}
getMyThirdItem { value in
queue.async {
total = value // on serial queue
}
}
...
}
有多種替代同步機制(鎖、GCD 讀寫器actor等)。但我從串行佇列示例開始觀察,實際上,任何串行佇列都可以完成相同的事情。許多人使用主佇列(這是一個串行佇列)進行這種微不足道的同步,其中性能影響可以忽略不計,例如在本例中。
例如,因此可以重構getMySecondItem以在主佇列上呼叫其完成處理程式,就像getMyFirstItem并且getMyThirdItem已經這樣做了。或者如果你不能這樣做,你可以簡單地讓getMySecondItem呼叫者調度需要同步到主佇列的代碼:
func addUpValues() {
var total = 0
getMyFirstItem { value in
total = value // on main thread
}
getMySecondItem { value in
DispatchQueue.main.async {
total = value // now on main thread, too
}
}
getMyThirdItem { value in
total = value // on main thread
}
// ...
}
That is also thread-safe. This is why many libraries will ensure that all of their completion handlers are called on the main thread, as it minimizes the amount of time the app developer needs to manually synchronize values.
While I have illustrated the use of serial dispatch queues for synchronization, there are a multitude of alternatives. E.g., one might use locks or GCD reader-writer pattern.
The key is that one should never mutate a variable from multiple threads without some synchronization.
Above I mention that you need to know when the three asynchronous tasks are done. You can use a DispatchGroup, e.g.:
func addUpValues(complete: @escaping (Int) -> Void) {
let total = Synchronized(0)
let group = DispatchGroup()
group.enter()
getMyFirstItem { first in
total.synchronized { value in
value = first
}
group.leave()
}
group.enter()
getMySecondItem { second in
total.synchronized { value in
value = second
}
group.leave()
}
group.enter()
getMyThirdItem { third in
total.synchronized { value in
value = third
}
group.leave()
}
group.notify(queue: .main) {
let value = total.synchronized { $0 }
complete(value)
}
}
And in this example, I abstracted the synchronization details out of addUpValues:
class Synchronized<T> {
private var value: T
private let lock = NSLock()
init(_ value: T) {
self.value = value
}
func synchronized<U>(block: (inout T) throws -> U) rethrows -> U {
lock.lock()
defer { lock.unlock() }
return try block(&value)
}
}
Obviously, use whatever synchronization mechanism you want (e.g., GCD or os_unfair_lock or whatever).
But the idea is that in the GCD world, dispatch groups can notify you when a series of asynchronous tasks are done.
I know that this was a GCD question, but for the sake of completeness, the Swift concurrency async-await pattern renders much of this moot.
func getMyFirstItem() async -> Int {
return 10
}
func getMySecondItem() async -> Int {
await Task.detached(priority: .background) {
return 10
}.value
}
func getMyThirdItem() async -> Int {
return 10
}
func addUpValues() {
Task {
async let value1 = getMyFirstItem()
async let value2 = getMySecondItem()
async let value3 = getMyThirdItem()
let total = await value1 value2 value3
print(total)
}
}
Or, if your async methods were updating some shared property, you would use an actor to synchronize access. See Protect mutable state with Swift actors.
uj5u.com熱心網友回復:
我可能是錯的,但我認為不同的佇列沒有太大區別,您仍然必須“等待”直到完成完成,例如:
var myItemsTotal: Int = 0
getMyFirstItem() { val1 in
getMySecondtItem() { val2 in
getMyThirdItem() { val3 in
myItemsTotal = val1 val2 val3
print(" myItemsTotal: \(myItemsTotal)")
}
}
}
uj5u.com熱心網友回復:
func getMyFirstItem(complete: @escaping (Int) -> Void) {
DispatchQueue.main.async {
print("you are on main thread")
complete(10)
}
}
func getMySecondtItem(complete: @escaping (Int) -> Void) {
DispatchQueue.global(qos:.background).async {
print("you are on background thread")
complete(10)
}
}
func getMyThirdItem(complete: @escaping (Int) -> Void) {
DispatchQueue.main.async {
print("you are on main thread 2")
complete(10)
}
}
你可以給出一個列印陳述句來查看執行流程,在函式呼叫的值改變列印值
getMyFirstItem(complete: {
value in
myItemsTotal = value
print(myItemsTotal)
})
getMySecondtItem(complete: {
value in
myItemsTotal = value
print(myItemsTotal)
})
getMyThirdItem(complete: {
value in
myItemsTotal = value
print(myItemsTotal)
})
每個執行緒都有自己的優先級,如果一個任務需要更多時間來執行,我們應該將該任務添加到后臺執行緒中。如果需要較少時間執行的任務我們可以將它們添加到主執行緒中。這一切都取決于任務執行時間。
請記住,UI 更新應該只發生在主執行緒中
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/395129.html
