【RxMvvmLight】0x09 订阅管理和生命周期
做一个处理订阅的小东西
到目前为止,在 RxMvvmLight 里,已经有各种各样的订阅了,但还没有取消订阅的设计,这是很不合理的,毕竟我们总是会担心内存泄漏。
先看 ReactiveUI 的设计
RxUI 使用 WhenActivated 来处理订阅,用法是这样的:
1
2
3
4
this.WhenActivated(d =>
{
something.Subscribe(...).DisposeWith(d);
})
WhenActivated 会提供一个在 Deactivated 时触发的 CompositeDisposable 对象,我们可以通过 DisposeWith 方法将我们的订阅也绑定在这个 CompositeDisposable 上,等 Deactivated 时一起释放掉。
我的思路
我其实觉得 WhenActivated 这种思路挺好的,但我也有自己的偏好,我觉得 WhenActivated 其实不必兼职作用域的活,我们其实完全可以写 DisposeWithDeactivated,麻烦不到哪去:
1
2
3
4
5
6
this.WhenActivated(() =>
{
something.Subscribe(...).DisposeWithDeactivated(this); // 失活时释放
something.Subscribe(...).DisposeWithDestroy(this); // 销毁时释放
something.Subscribe(...).DisposeWithMessage(this); // 指定消息到达时释放
})
但好处却是显而易见的:
- 强调了释放时机。
- 可以注册 this 之外的其他生命周期,不必绑死在 this 上。
- 可以提供更丰富的生命周期语义。
- 如果有其他的释放时机,可以使用同样的语法,比如我们可以实现一个 DisposeWithMessage 在收到消息时释放。
我个人认为这样应该是比较灵活的。
总结一下就是,我决定多造几个 WhenXXX 这样式的生命周期方法,然后再造一些 DisposeWithXXX 这样的清理方法,让他们配套使用
先造一个能触发的信号
为啥要造一个这东西呢?因为我发现一个问题,C# 的事件没法当变量用,Observable<T>倒是可以,但是在我的框架里,最常见的 Observable<T> 来自 PropertyChanged 事件,并且会发送当前值,如果我实现了一个 DisposeWithObservable,那在使用这个方法的时候就得仔细确认一下,需不需要skip(1), 我觉得这是毫无意义的认知负担,不如另外弄一个能无脑用的。
所以,我按照事件的语义,制作了一个简单的信号类型,用它来代替事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public interface IAsyncSignal
{
IDisposable Subscribe(Func<ValueTask> action);
}
internal class AsyncSignal : IAsyncSignal
{
private readonly Lock gate = new();
private readonly List<Func<ValueTask>> handlers = [];
public async Task InvokeAsync()
{
// 复制一份,防止在执行中途取消订阅导致报错
// 换言之:只要触发了,必须全部执行(除非报错),不许执行一半突然决定不执行了
// 如果有类似需求,让他自己用 Canceltoken 搞。
List<Func<ValueTask>> snapshot;
lock (gate)
{
snapshot = [.. this.handlers];
}
foreach (var item in snapshot)
{
await item.Invoke();
}
}
public IDisposable Subscribe(Func<ValueTask> handler)
{
lock (gate)
{
handlers.Add(handler);
}
return Disposable.Create(() =>
{
lock (gate)
{
handlers.Remove(handler);
}
});
}
public void Clear()
{
lock (gate)
{
handlers.Clear();
}
}
}
为啥是异步的呢?因为我希望每个订阅是串行处理的,但如果是同步方法但是异步订阅,就不一定串行了,而且async void 也没办法往上抛异常。
制作生命周期事件
我决定制作四个生命周期事件:初始化/激活/失活/销毁,这应该可以覆盖一个页面最常见的需求,更多事件就不实用了。
在这里,我做了一个小小的状态机,保证状态可以按顺序变化,比如 Activate 时,一定已经Initialized 完毕,Deactivate 时,一定已经 Activated,这样可以避免一些奇奇怪怪的竞争,同时也保证在 Activate 时注册的订阅可以可靠的在 Deactivate 时取消。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public enum LifecycleState
{
Created,
Initialized,
Activated,
Destroyed,
}
public class ViewModelLifecycle
{
private readonly Lock _gate = new();
private readonly AsyncSignal _initializing = new();
private readonly AsyncSignal _activating = new();
private readonly AsyncSignal _deactivating = new();
private readonly AsyncSignal _destroying = new();
public IAsyncSignal Initializing => _initializing;
public IAsyncSignal Activating => _activating;
public IAsyncSignal Deactivating => _deactivating;
public IAsyncSignal Destroying => _destroying;
public LifecycleState State { get; private set; }
private Task _tail = Task.CompletedTask;
private Task EnqueueAsync(Func<Task> action)
{
lock (_gate)
{
async Task GetTask()
{
try
{
// 等待正在执行的任务完成
await _tail;
}
catch { } // 我们不关心前一个任务有没有异常,完成就行
await action();
}
return _tail = GetTask();
}
}
public async Task InitializeAsync()
{
await EnqueueAsync(async () =>
{
await Transition(LifecycleState.Created, LifecycleState.Initialized, _initializing);
});
}
public async Task ActivateAsync(bool ensureInitialized = true)
{
await EnqueueAsync(async () =>
{
if (ensureInitialized)
await Transition(LifecycleState.Created, LifecycleState.Initialized, _initializing);
await Transition(LifecycleState.Initialized, LifecycleState.Activated, _activating);
});
}
public async Task DeactivateAsync()
{
await EnqueueAsync(async () =>
{
await Transition(LifecycleState.Activated, LifecycleState.Initialized, _deactivating);
});
}
public async Task DestroyAsync()
{
await EnqueueAsync(async () =>
{
await Transition(LifecycleState.Activated, LifecycleState.Initialized, _deactivating);
await Transition(LifecycleState.Initialized, LifecycleState.Destroyed, _destroying);
await Transition(LifecycleState.Created, LifecycleState.Destroyed, _destroying);
});
}
private async Task Transition(LifecycleState from, LifecycleState to, AsyncSignal signal)
{
if (State == from)
{
try
{
await signal.InvokeAsync();
}
finally
{
State = to;
}
}
}
}
辅助方法和处理订阅
这里就是一些辅助方法了,逻辑一看就懂,就不细说了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public interface IHasLifecycle
{
ViewModelLifecycle Lifecycle { get; }
}
public static class LifecycleExtensions
{
extension(IHasLifecycle self)
{
public IDisposable WhenInitialize(Action action)
=> self.WhenInitialize(action.AsValueTask());
public IDisposable WhenInitialize(Func<ValueTask> action)
=> self.Lifecycle.Initializing.Subscribe(action);
public IDisposable WhenActivate(Action action)
=> self.WhenActivate(action.AsValueTask());
public IDisposable WhenActivate(Func<ValueTask> action)
=> self.Lifecycle.Activating.Subscribe(action);
public IDisposable WhenDeactivate(Action action)
=> self.WhenDeactivate(action.AsValueTask());
public IDisposable WhenDeactivate(Func<ValueTask> action)
=> self.Lifecycle.Deactivating.Subscribe(action);
public IDisposable WhenDestroy(Action action)
=> self.WhenDestroy(action.AsValueTask());
public IDisposable WhenDestroy(Func<ValueTask> action)
=> self.Lifecycle.Destroying.Subscribe(action);
}
extension(IDisposable self)
{
public IDisposable DisposeWith(IAsyncSignal signal)
{
// 取消订阅这个动作本身也是一个订阅
// 这里让这个订阅完成任务后取消自身
IDisposable disposable = null!;
disposable = signal.Subscribe(() =>
{
try
{
self.Dispose();
}
finally
{
disposable.Dispose();
}
return ValueTask.CompletedTask;
});
// 返回原始 IDisposable 方便链式挂载到其他触发源。
return self;
}
public IDisposable DisposeWithDeactivate(IHasLifecycle vm)
=> self.DisposeWith(vm.Lifecycle.Deactivating);
public IDisposable DisposeWithDeactivate(ViewModelLifecycle lifecycle)
=> self.DisposeWith(lifecycle.Deactivating);
public IDisposable DisposeWithDestroy(IHasLifecycle vm)
=> self.DisposeWith(vm.Lifecycle.Destroying);
public IDisposable DisposeWithDestroy(ViewModelLifecycle lifecycle)
=> self.DisposeWith(lifecycle.Destroying);
}
extension(IAsyncDisposable self)
{
public IAsyncDisposable DisposeWith(IAsyncSignal signal)
{
// 取消订阅这个动作本身也是一个订阅
// 这里让这个订阅完成任务后取消自身
IDisposable disposable = null!;
disposable = signal.Subscribe(async () =>
{
try
{
await self.DisposeAsync();
}
finally
{
disposable.Dispose();
}
});
// 返回原始 IDisposable 方便链式挂载到其他触发源。
return self;
}
public IAsyncDisposable DisposeWithDeactivate(IHasLifecycle vm)
=> self.DisposeWith(vm.Lifecycle.Deactivating);
public IAsyncDisposable DisposeWithDeactivate(ViewModelLifecycle lifecycle)
=> self.DisposeWith(lifecycle.Deactivating);
public IAsyncDisposable DisposeWithDestroy(IHasLifecycle vm)
=> self.DisposeWith(vm.Lifecycle.Destroying);
public IAsyncDisposable DisposeWithDestroy(ViewModelLifecycle lifecycle)
=> self.DisposeWith(lifecycle.Destroying);
}
}