【问题标题】:Should I make sure an object is unusable after Dispose was called?我是否应该确保对象在调用 Dispose 后不可用?
【发布时间】:2023-01-10 04:20:08
【问题描述】:

我有一个类 BleScanner,它包装了一个内部 BluetoothLEAdvertisementWatcher。它还实现了 IDisposable 以确保在扫描仪被处理时停止观察者。

public sealed class BleScanner : IDisposable
{
    public event AdvertisementReceivedHandler? AdvertisementReceived;

    private readonly BluetoothLEAdvertisementWatcher m_Watcher;

    public BleScanner() {
        m_Watcher = new() {
            // ...
        };
        // m_Watcher.Received += OnAdvertisementReceived;
    }

    // private void OnAdvertisementReceived(...) {
    //    code elided for brevity
    //    may eventually raise AdvertisementReceived
    // }

    public void Start() => m_Watcher.Start();

    public void Stop() => m_Watcher.Stop();

    public void Dispose() {
        if (m_Watcher.Status == BluetoothLEAdvertisementWatcherStatus.Started) {
            m_Watcher.Stop();
        }
    }
}

观察者不是一次性的。所以理论上,如果你在Dispose之后再次调用Start,扫描器仍然可以工作:

public async Task ScannerTest(CancellationToken token) {
    using var scanner = new BleScanner();
    scanner.AdvertisementReceived += OnAdvertisementReceived;

    scanner.Start(); // will start the scan
    await Task.Delay(3000, token); // raise events for 3 seconds
    scanner.Stop(); // could be forgotten
    scanner.Dispose(); // will stop the scan if indeed it was forgotten
    
    scanner.Start(); // everything will work, despite "scanner" being disposed already
}

我是否应该确保Start(也许Stop)在Dispose被调用后抛出ObjectDisposedExceptionguidelines on the Dispose pattern 只要求Dispose 可以被调用多次而不会出现异常,但不说其他成员在Dispose 被调用后应该如何表现。 IDisposable interfaceusing disposable objects 也没有说明在已处置对象上调用方法时会发生什么。

【问题讨论】:

  • “观察者不是一次性的”- 为什么你的班级是IDisposable? -“它还实现了 IDisposable 以确保在扫描仪被处理时停止观察者。”- 这不是实施IDisposable的好理由,imo。不过,我不能提出任何更好的建议,因为不幸的是 C# 不(还)支持线性类型。
  • 意见可能会有所不同,但对我来说,如果您的实例不持有一次性物品,那么您就是在对班级的消费者施加人为约束。
  • 但是要回答这个问题,IMO 如果它在罐头上写着IDisposable,那么我会预计ObjectDisposedException 如果它是在处理后使用的。
  • @Dai 但是,如果没有人想再使用结果,为什么观察者要继续占用 BLE 天线上的一个时隙进行扫描呢?
  • @LWChris 我同意你的看法:不应该——但这不是我的意思。我的观点是,我不认为IDisposable 一定是最好沟通方式合同要求给图书馆的消费者。也许如果您为您的class BleScanner 分享一些示例用例,我们可以提出一些更好的建议。 (例如,短暂的单子行为可以更好地表示为 Task(或 Task<T>,如果它有有意义的结果)(这确实不是暗示也不要求使用 async 修饰符 BTW)。

标签: c# idisposable


【解决方案1】:

在您的问题中,您引用了IDisposable Guidelines。第一行说“实现 Dispose 方法主要是为了释放非托管资源”。我不认为那是你在这里做的。如果 BluetoothLEAdvertisementWatcher 是 IDisposable,那么您可以在 Dispose() 函数中处理它;但事实并非如此。因此,垃圾回收将在您的对象超出范围后及时处理您的对象;让它做它的事情。

希望有所帮助。

【讨论】:

  • 我最近做了很多嵌入式编程,并学会了重视无线电时间等资源。扫描 BLE 设备占据了 BLE 循环通信时隙的很大一部分,因此我将该时隙视为非托管资源,并希望确保在 GC 最终到来之前不会有流氓扫描器吃掉 BLE 性能,或者该程序死于老年。
【解决方案2】:

使用IDisposable 来释放托管资源是完全没问题的,这真的任何一种需要在该范围的末尾运行代码的范围。

在这种情况下,我会说范围实际上在StartStop 附近。所以我会让Start返回一个调用StopIDisposable(并使Stop私有)。你的类型不会是一次性的。例如,使用我的 Nito.Disposables 库中的 Disposable

public sealed class BleScanner
{
    public event AdvertisementReceivedHandler? AdvertisementReceived;

    private readonly BluetoothLEAdvertisementWatcher m_Watcher;

    public BleScanner() {
        m_Watcher = new() {
            // ...
        };
        // m_Watcher.Received += OnAdvertisementReceived;
    }

    public IDisposable Start()
    {
        m_Watcher.Start();
        return Disposable.Create(() => Stop());
    }

    private void Stop() => m_Watcher.Stop();
}

public async Task ScannerTest(CancellationToken token) {
    var scanner = new BleScanner();
    scanner.AdvertisementReceived += OnAdvertisementReceived;

    using var scannerSubsctiption = scanner.Start();
    await Task.Delay(3000, token); // raise events for 3 seconds
}

【讨论】:

  • 这有点接近我早期的 BleScanner 草稿之一。它是一个静态类(基本上是一个扫描工厂),您可以调用“InfiniteScan”并获得一个具有“开始”和“停止”以及要订阅的事件的对象,或者使用 TimeSpan 获取“TimedScan”并且有 even 和 Task Run(CancellationToken) 方法,任务将在超时后完成。但后来我想如果我可能有多个观察者,那为什么不用多个扫描器呢?这个在中间,多个扫描仪,基本上“开始”是一次性的东西。
猜你喜欢
  • 2011-06-30
  • 1970-01-01
  • 2011-11-23
  • 2011-04-13
  • 2015-09-07
  • 2023-04-01
  • 1970-01-01
  • 2013-01-23
  • 1970-01-01
相关资源
最近更新 更多