你说:
然而,事实并非如此:每0.5秒就会有countText
次更新,就好像没有发生取消一样.
有几个问题:
In your example, the Text
isn’t showing countText
, but rather "\(count)"
. So you are not watching countText
being updated, but rather seeing count
get updated. You do not appear to be using countText
anywhere.
因此,使用Text(countText)
而不是Text("\(count)")
.
[The已编辑问题以删除此问题.]
另外,由于您正在使用try? await Task.sleep(…)
,因此当task
取消时,Task.sleep(…)
会立即返回,但不会退出Task {…}
.由于没有发现错误,因此它只会继续到Task {…}
中的下一行,即更新countText
的行.
但这可能不是你的本意.如果您真的希望cancel
停止整个Task {…}
的执行,那么简单的解决方案是使用try
而不是try?
.然后,Task {…}
将发现错误并立即退出,而不是继续到Task
中的下一行.如果您这样做,您也会将task
定义为Task<(), Error>
而不是Task<(), Never>
.
因此,也许:
struct ContentView: View {
@State private var count = 0
@State private var countText = "0"
@State private var task: Task<(), Error>?
var body: some View {
Text(countText)
.task {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) {
task?.cancel()
task = Task {
try await Task.sleep(for: .seconds(2.0))
countText = "\(count)"
}
count += 1
if count == 20 {
$0.invalidate()
}
}
}
}
}
这将:
- 每0.5秒更新
count
;
- 仅在
countText
更改时更新Text
;
countText
仅在任务未取消时才会更新;
- 因此,它不会更新
countText
,直到最后一次触发计时器(因为所有其他计时都被取消了).
有一个小问题仍然存在.如果该观点被驳回怎么办?人们可以考虑用withTaskCancellationHandler
来包装它.或者我想你可以在.onDisappear
中取消计时器.
就我个人而言,我会更进一步,完全退休Timer
人.
简单的解决方案是循环:
struct ContentView: View {
@State private var count = 0
@State private var countText = "0"
var body: some View {
Text(countText)
.task {
do {
for _ in 0 ..< 20 {
try await Task.sleep(for: .seconds(0.5))
count += 1
task?.cancel()
task = Task {
try await Task.sleep(for: .seconds(2.0))
countText = "\(count)"
}
}
} catch {
// if this view is dismissed, this whole `.task` will get
// canceled automatically; but because you have employed
// unstructured concurrency, we will need to cancel that
// `Task` manually.
task?.cancel()
}
}
}
}
还有其他模式(例如AsyncTimerSequence
/Swift Async 算法rithms).但关键点是我们希望留在Swift并发中,而不是退回到遗留模式.
就其价值而言,我们通常建议不要从同步上下文中引入非 struct 化并发.您问题的整个前提是手动取消非 struct 化并发,所以希望我已经回答了上面的问题,但我们应该注意,这种模式是我们尽可能避免的.
别误会我的意思.非 struct 化并发具有实用性.它为我们提供了Swift并发系统中的最终控制权.但是,除其他外,考虑到它的脆弱程度,我们通常希望避免它(例如,忽略某些我们未能手动取消的执行路径是多么容易).