成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

在 Swift 中使用 async let 并發運行后臺任務

開發 前端
Swift異步編程是一種編寫允許某些任務并發運行而不是按順序運行的代碼的方法。這可以提高應用程序的性能,允許它同時執行多個任務,但更重要的是,它可以用來確保用戶界面對用戶輸入的響應,同時任務在后臺線程上執行。

前言

Async/await語法是在Swift 5.5 引入的,在 WWDC 2021中的 Meet async/await in Swift[1] 對齊進行了介紹。它是編寫異步代碼的一種更可讀的方式,比調度隊列和回調函數更容易理解。Async/await 語法與其他編程語言(如C#或JavaScript)中使用的語法類似。使用 "async let "是為了并行的運行多個后臺任務,并等待它們的綜合結果。

Swift異步編程是一種編寫允許某些任務并發運行而不是按順序運行的代碼的方法。這可以提高應用程序的性能,允許它同時執行多個任務,但更重要的是,它可以用來確保用戶界面對用戶輸入的響應,同時任務在后臺線程上執行。

長期運行的任務阻塞了UI

在一個同步的程序中,代碼以線性的、從上到下的方式運行。程序等待當前任務完成后再進入下一任務。這在用戶界面(UI)方面會產生問題,因為如果一個長期運行的任務被同步執行,程序就會阻塞,UI就會變得沒有反應,直到任務完成。

下面的代碼模擬了一個長期運行的任務,如以同步方式下載一個文件,其結果是UI 變得沒有反應,直到任務完成。這樣的用戶體驗是不可接受的。

Model:

struct DataFile : Identifiable, Equatable {
    var id: Int
    var fileSize: Int
    var downloadedSize = 0
    var isDownloading = false
    
    init(id: Int, fileSize: Int) {
        self.id = id
        self.fileSize = fileSize
    }
    
    var progress: Double {
        return Double(self.downloadedSize) / Double(self.fileSize)
    }
    
    mutating func increment() {
        if downloadedSize < fileSize {
            downloadedSize += 1
        }
    }
}

ViewModel:

class DataFileViewModel: ObservableObject {
    @Published private(set) var file: DataFile
    
    init() {
        self.file = DataFile(id: 1, fileSize: 10)
    }
    
    func downloadFile() {
        file.isDownloading = true

        for _ in 0..<file.fileSize {
            file.increment()
            usleep(300000)
        }

        file.isDownloading = false
    }
    
    func reset() {
        self.file = DataFile(id: 1, fileSize: 10)
    }
}

View:

struct TestView1: View {
    @ObservedObject private var dataFiles: DataFileViewModel
    
    init() {
        dataFiles = DataFileViewModel()
    }
    
    var body: some View {
        VStack {
            /// 從文末源代碼獲取其實現
            TitleView(title: ["Synchronous"])
            
            Button("Download All") {
                dataFiles.downloadFile()
            }
            .buttonStyle(BlueButtonStyle())
            .disabled(dataFiles.file.isDownloading)
            
            HStack(spacing: 10) {
                Text("File 1:")
                ProgressView(value: dataFiles.file.progress)
                    .frame(width: 180)
                Text("\((dataFiles.file.progress * 100), specifier: "%0.0F")%")

                ZStack {
                    Color.clear
                        .frame(width: 30, height: 30)
                    if dataFiles.file.isDownloading {
                        ProgressView()
                            .progressViewStyle(CircularProgressViewStyle(tint: .blue))
                    }
                }
            }
            .padding()
            
            Spacer().frame(height: 200)

            Button("Reset") {
                dataFiles.reset()
            }
            .buttonStyle(BlueButtonStyle())

            Spacer()
        }
        .padding()
    }
}

圖片

模擬同步下載一個文件--沒有實時更新UI

在后臺執行任務

將 ViewModel 中的downloadFile方法修改為異步的。請注意,由于DataFile模型是被視圖監聽的,對模型的任何改變都需要在UI線程上執行。這是通過使用 MainActor[2] 隊列來完成的,即用MainActor.run包裹所有的模型更新。

ViewModel

class DataFileViewModel2: ObservableObject {
    @Published private(set) var file: DataFile
    
    init() {
        self.file = DataFile(id: 1, fileSize: 10)
    }
    
    func downloadFile() async -> Int {
        await MainActor.run {
            file.isDownloading = true
        }
        
        for _ in 0..<file.fileSize {
            await MainActor.run {
                file.increment()
            }
            usleep(300000)
        }
        
        await MainActor.run {
            file.isDownloading = false
        }
        
        return 1
    }
    
    func reset() {
        self.file = DataFile(id: 1, fileSize: 10)
    }
}

View:

struct TestView2: View {
    @ObservedObject private var dataFiles: DataFileViewModel2
    @State var fileCount = 0
    
    init() {
        dataFiles = DataFileViewModel2()
    }
    
    var body: some View {
        VStack {
            TitleView(title: ["Asynchronous"])
            
            Button("Download All") {
                Task {
                    let num = await dataFiles.downloadFile()
                    fileCount += num
                }
            }
            .buttonStyle(BlueButtonStyle())
            .disabled(dataFiles.file.isDownloading)
            
            Text("Files Downloaded: \(fileCount)")
            
            HStack(spacing: 10) {
                Text("File 1:")
                ProgressView(value: dataFiles.file.progress)
                    .frame(width: 180)
                Text("\((dataFiles.file.progress * 100), specifier: "%0.0F")%")
                
                ZStack {
                    Color.clear
                        .frame(width: 30, height: 30)
                    if dataFiles.file.isDownloading {
                        ProgressView()
                            .progressViewStyle(CircularProgressViewStyle(tint: .blue))
                    }
                }
            }
            .padding()
            
            Spacer().frame(height: 200)
            
            Button("Reset") {
                dataFiles.reset()
            }
            .buttonStyle(BlueButtonStyle())
            
            Spacer()
        }
        .padding()
    }
}

使用 async/await 來模擬下載一個文件,同時更新UI

在后臺執行多個任務

現在我們有一個文件在后臺下載,UI顯示進度,讓我們把它改為多個文件。ViewModel被改為持有一個DataFiles數組,而不是一個單一的文件。添加一個downloadFiles方法來遍歷所有文件并下載每一個。

視圖被綁定到DataFiles數組,并更新顯示每個文件的下載進度。下載按鈕被綁定到異步的downloadFiles中。

ViewModel:

class DataFileViewModel3: ObservableObject {
    @Published private(set) var files: [DataFile]
    @Published private(set) var fileCount = 0
    
    init() {
        files = [
            DataFile(id: 1, fileSize: 10),
            DataFile(id: 2, fileSize: 20),
            DataFile(id: 3, fileSize: 5)
        ]
    }
    
    var isDownloading : Bool {
        files.filter { $0.isDownloading }.count > 0
    }
    
    func downloadFiles() async {
        for index in files.indices {
            let num = await downloadFile(index)
            await MainActor.run {
                fileCount += num
            }
        }
    }
    
    private func downloadFile(_ index: Array<DataFile>.Index) async -> Int {
        await MainActor.run {
            files[index].isDownloading = true
        }
        
        for _ in 0..<files[index].fileSize {
            await MainActor.run {
                files[index].increment()
            }
            usleep(300000)
        }
        await MainActor.run {
            files[index].isDownloading = false
        }
        return 1
    }
    
    func reset() {
        files = [
            DataFile(id: 1, fileSize: 10),
            DataFile(id: 2, fileSize: 20),
            DataFile(id: 3, fileSize: 5)
        ]
    }
}

View:

struct TestView3: View {
    @ObservedObject private var dataFiles: DataFileViewModel3
    
    init() {
        dataFiles = DataFileViewModel3()
    }
    
    var body: some View {
        VStack {
            TitleView(title: ["Asynchronous", "(multiple Files)"])
            
            Button("Download All") {
                Task {
                    await dataFiles.downloadFiles()
                }
            }
            .buttonStyle(BlueButtonStyle())
            .disabled(dataFiles.isDownloading)
            
            Text("Files Downloaded: \(dataFiles.fileCount)")
            
            ForEach(dataFiles.files) { file in
                HStack(spacing: 10) {
                    Text("File \(file.id):")
                    ProgressView(value: file.progress)
                        .frame(width: 180)
                    Text("\((file.progress * 100), specifier: "%0.0F")%")
                    
                    ZStack {
                        Color.clear
                            .frame(width: 30, height: 30)
                        if file.isDownloading {
                            ProgressView()
                                .progressViewStyle(CircularProgressViewStyle(tint: .blue))
                        }
                    }
                }
            }
            .padding()
            
            Spacer().frame(height: 150)
            
            Button("Reset") {
                dataFiles.reset()
            }
            .buttonStyle(BlueButtonStyle())
            
            Spacer()
        }
        .padding()
    }
}

圖片

使用async await來模擬按順序下載多個文件

下載多個文件

使用 "async let "來模擬并發下載多個文件的情況。

上面的代碼可以被改進,以并行地執行多個下載,因為每個任務都是獨立于其他任務的。在Swift并發中,這是用async let實現的,它用一個承諾立即給一個變量賦值,允許代碼執行下一行代碼。然后,代碼等待這些承諾,等待最終結果的完成。

async/await:

func downloadFiles() async {
        for index in files.indices {
            let num = await downloadFile(index)
            await MainActor.run {
                fileCount += num
            }
        }
    }

async let:

func downloadFiles() async {
        async let num1 = await downloadFile(0)
        async let num2 = await downloadFile(1)
        async let num3 = await downloadFile(2)
        
        let (result1, result2, result3) = await (num1, num2, num3)
        await MainActor.run {
            fileCount = result1 + result2 + result3
        }
    }

ViewModel:

class DataFileViewModel4: ObservableObject {
    @Published private(set) var files: [DataFile]
    @Published private(set) var fileCount = 0
    
    init() {
        files = [
            DataFile(id: 1, fileSize: 10),
            DataFile(id: 2, fileSize: 20),
            DataFile(id: 3, fileSize: 5)
        ]
    }
    
    var isDownloading : Bool {
        files.filter { $0.isDownloading }.count > 0
    }
    
    func downloadFiles() async {
        async let num1 = await downloadFile(0)
        async let num2 = await downloadFile(1)
        async let num3 = await downloadFile(2)
        
        let (result1, result2, result3) = await (num1, num2, num3)
        await MainActor.run {
            fileCount = result1 + result2 + result3
        }
    }
    
    private func downloadFile(_ index: Array<DataFile>.Index) async -> Int {
        await MainActor.run {
            files[index].isDownloading = true
        }
        
        for _ in 0..<files[index].fileSize {
            await MainActor.run {
                files[index].increment()
            }
            usleep(300000)
        }
        await MainActor.run {
            files[index].isDownloading = false
        }
        return 1
    }
    
    
    func reset() {
        files = [
            DataFile(id: 1, fileSize: 10),
            DataFile(id: 2, fileSize: 20),
            DataFile(id: 3, fileSize: 5)
        ]
    }
}

View:

struct TestView4: View {
    @ObservedObject private var dataFiles: DataFileViewModel4
    
    init() {
        dataFiles = DataFileViewModel4()
    }
    
    var body: some View {
        VStack {
            TitleView(title: ["Parallel", "(multiple Files)"])
            
            Button("Download All") {
                Task {
                    await dataFiles.downloadFiles()
                }
            }
            .buttonStyle(BlueButtonStyle())
            .disabled(dataFiles.isDownloading)
            
            Text("Files Downloaded: \(dataFiles.fileCount)")
            
            ForEach(dataFiles.files) { file in
                HStack(spacing: 10) {
                    Text("File \(file.id):")
                    ProgressView(value: file.progress)
                        .frame(width: 180)
                    Text("\((file.progress * 100), specifier: "%0.0F")%")
                    
                    ZStack {
                        Color.clear
                            .frame(width: 30, height: 30)
                        if file.isDownloading {
                            ProgressView()
                                .progressViewStyle(CircularProgressViewStyle(tint: .blue))
                        }
                    }
                }
            }
            .padding()
            
            Spacer().frame(height: 150)
            
            Button("Reset") {
                dataFiles.reset()
            }
            .buttonStyle(BlueButtonStyle())
            
            Spacer()
        }
        .padding()
    }
}

圖片

使用 "async let "來模擬并行下載多個文件的情況

結論

在后臺執行長期運行的任務并保持UI的響應是很重要的。async/await提供了一個干凈的機制來執行異步任務。有的時候,一個方法在后臺調用多個方法,默認情況下是按順序進行這些調用。async 讓其立即返回,允許代碼進行下一個調用,然后所有返回的對象可以一起等待。這使得多個后臺任務可以并行進行。

GitHub 上提供了 AsyncLetApp[3] 的源代碼。

參考資料

[1]Meet async/await in Swift - introduction video from Apple: https://developer.apple.com/videos/play/wwdc2021/10132/。

[2]MainActor: https://developer.apple.com/documentation/swift/mainactor/。

[3]Async Let app source code on GitHub: https://github.com/SwiftCommunityRes/swiftAsyncBackgroundTasks。

責任編輯:姜華 來源: Swift社區
相關推薦

2023-11-27 19:32:25

Android

2023-11-25 09:30:13

Android后臺任務

2022-01-10 09:05:32

Linux后臺命令

2022-04-26 08:41:38

Swift并發系統iOS

2023-11-06 14:13:51

asyncio開發

2018-09-12 21:25:15

iOSAppcrash

2023-11-17 12:04:39

GORM并發

2020-03-11 09:15:25

微信asyncawait

2009-06-19 20:38:49

Linux操作系統

2014-08-01 15:16:05

SwiftC語言

2015-04-17 16:44:22

swiftOC

2015-08-27 09:46:09

swiftAFNetworkin

2014-07-02 09:47:06

SwiftCocoaPods

2022-11-04 09:01:33

SwiftPlottable

2025-03-21 09:01:34

Swift任務取消機制協作式取消

2021-10-13 07:30:13

AndroidAlarmManageWorkManager

2014-07-04 09:47:24

SwiftSwift開發

2025-04-15 08:20:00

FastAPI異步函數

2009-07-03 12:59:40

Servlet配置

2023-12-01 08:21:51

開發者Android組件庫
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产乱码精品1区2区3区 | 欧美激情精品久久久久久 | 日本午夜一区二区三区 | 国产精品久久久久久二区 | 99视频在线免费观看 | 国产成人精品网站 | 一区二区三区高清不卡 | 久久精品一 | 日日操夜夜操天天操 | 天天插天天搞 | 国产成人精品高清久久 | 久久精品日产第一区二区三区 | 久久久青草婷婷精品综合日韩 | 亚洲精品乱码久久久久久9色 | 久久av资源网| 第四色狠狠 | 69堂永久69tangcom | 日本一本视频 | 日本精品久久久一区二区三区 | 国产一区二区在线播放 | 日本在线视 | 国产成人福利在线观看 | www.天天操.com| 国产粉嫩尤物极品99综合精品 | 99视频久| 在线免费观看毛片 | 欧美韩一区二区 | 在线视频一区二区 | 亚洲 欧美 精品 | 成人免费视频观看 | 欧美性jizz18性欧美 | 91精品久久| 国产在线视频一区二区 | 91av大全 | 国产91久久精品一区二区 | 91成人午夜性a一级毛片 | 国产精品久久 | 操一草 | 91精品久久久久久久久久 | 久久一区二区三区免费 | 国产精品国产三级国产aⅴ中文 |