文章

【RxMvvmLight】0x09 订阅管理和生命周期

做一个处理订阅的小东西

【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);      // 指定消息到达时释放
})

但好处却是显而易见的:

  1. 强调了释放时机。
  2. 可以注册 this 之外的其他生命周期,不必绑死在 this 上。
  3. 可以提供更丰富的生命周期语义。
  4. 如果有其他的释放时机,可以使用同样的语法,比如我们可以实现一个 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);
    }
}
本文由作者按照 CC BY 4.0 进行授权