我想下载一个大文件,了解传输的字节数,并能够在必要时取消下载。
我知道这可以通过 URLSessionDownloadTask
并符合 URLSessionDownloadDelegate
来完成,但我想通过 async/await
来实现它> 机制,所以我使用了 URLSession.shared.bytes(from: url)
,然后使用 for-await-in 循环来处理每个字节。
尝试取消正在进行的任务时会出现问题,因为即使 URLSession.AsyncBytes
的 Task
已被取消,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
请求被取消之前收到的。
您可能会考虑取消 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
被取消,它就会退出。
关于swift - 取消 URLSession.AsyncBytes 的任务似乎不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75464142/