swift - 取消 URLSession.AsyncBytes 的任务似乎不起作用

标签 swift async-await request cancellation

我想下载一个大文件,了解传输的字节数,并能够在必要时取消下载。

我知道这可以通过 URLSessionDownloadTask 并符合 URLSessionDownloadDelegate 来完成,但我想通过 async/await 来实现它> 机制,所以我使用了 URLSession.shared.bytes(from: url) ,然后使用 for-await-in 循环来处理每个字节。

尝试取消正在进行的任务时会出现问题,因为即使 URLSession.AsyncBytesTask 已被取消,for-await-in 循环仍会继续处理字节,所以我假设下载仍在进行中。

我已经在 Playground 上用这段代码对其进行了测试。

    let url = URL(string: "https://example.com/large_file.zip")!
        
    let (asyncBytes, _) = try await URLSession.shared.bytes(from: url)
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        asyncBytes.task.cancel()
    }

    var data = Data()
    
    for try await byte in asyncBytes {
        
        data.append(byte)
        print(data.count)
    }

我预计,一旦任务被取消,下载就会停止,因此 for-await-in 将停止处理字节。

我在这里缺少什么?难道这些任务就不能有效取消吗?

最佳答案

取消 URLSessionDataTask 可以与 AsyncBytes 配合使用。话虽如此,即使 URLSessionDataTask 被取消,AsyncBytes 也将继续迭代取消之前接收到的字节。但数据任务确实停止了。

考虑实验1:

@MainActor
class ViewModel: ObservableObject {
    private let url: URL = …
    private let session: URLSession = …
    private var cancelButtonTapped = false
    private var dataTask: URLSessionDataTask?

    @Published var bytesBeforeCancel = 0
    @Published var bytesAfterCancel = 0

    func experiment1() async throws {
        let (asyncBytes, _) = try await session.bytes(from: url)
        dataTask = asyncBytes.task

        var data = Data()

        for try await byte in asyncBytes {
            if cancelButtonTapped {
                bytesAfterCancel += 1
            } else {
                bytesBeforeCancel += 1
            }
            data.append(byte)
        }
    }

    func cancel() {
        dataTask?.cancel()
        cancelButtonTapped = true
    }
}

因此,我在 1 秒后取消了(此时我已经迭代了 2,022 个字节),并且它继续迭代在取消 URLSessionDataTask 之前收到的剩余 14,204 个字节。但下载确实成功停止。 (在我的示例中,下载的实际 Assets 为 74mb。)使用 URLSession 时,数据以数据包形式出现,因此需要 AsyncBytes 花费一点时间来完成所有内容实际上是在 URLSession 请求被取消之前收到的。

enter image description here


您可能会考虑取消 Swift 并发Task,而不是URLSessionDataTask。 (我真的希望他们不要使用同一个词“任务”来指代完全不同的概念!)

考虑实验2:

@MainActor
class ViewModel: ObservableObject {
    private let url: URL = …
    private let session: URLSession = …
    private var cancelButtonTapped = false
    private var task: Task<Void, Error>?

    @Published var bytesBeforeCancel = 0
    @Published var bytesAfterCancel = 0

    func experiment2() async throws {
        task = Task { try await download() }
        try await task?.value
    }

    func cancel() {
        task?.cancel()
        cancelButtonTapped = true
    }

    func download() async throws {
        let (asyncBytes, _) = try await session.bytes(from: url)

        var data = Data()

        for try await byte in asyncBytes {
            try Task.checkCancellation()

            if cancelButtonTapped {        // this whole `if` statement is no longer needed, but I've kept it here for comparison to the previous example
                bytesAfterCancel += 1
            } else {
                bytesBeforeCancel += 1
            }

            data.append(byte)
        }
    }
}

如果没有 try Task.checkCancellation() 行,其行为几乎与 experiment1 中相同。使用 AsyncBytes 取消 Task 将导致底层 URLSessionDataTask 的取消(但序列将继续迭代中的字节)取消之前成功接收的数据包)。但是使用try Task.checkCancellation(),一旦Task被取消,它就会退出。

enter image description here

关于swift - 取消 URLSession.AsyncBytes 的任务似乎不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75464142/

相关文章:

swift - Swift 3 的 SignInWithCredentials 给我带来了 Firebase for Facebook 登录的麻烦

swift - 使用动画设置 WKInterfacePicker 高度选择任意项目

multithreading - await Task.WhenAll() 与 Task.WhenAll().Wait()

javascript - 使用 Async/Await 自定义错误处理

java - 手动向网站发送 GET 请求。 302重定向错误

php - 如何在zend框架2或AjaxContext中使用ajax?

ios - 为什么我在 Controller 代码之前执行 View 代码?

iOS ResearchKit swift 文档

javascript - 为什么我的异步函数返回一个空数组?

javascript - NodeJS : How can I retrieve a parameter when send to a promise