【C#】定时器保活机制引起的内存泄露问题

C# 中有三种定时器,System.Windows.Forms 中的定时器和 System.Timers.Timer 的工作方式是完全一样的,所以,这里我们仅讨论 System.Timers.TimerSystem.Threading.Timer

1、定时器保活

先来看一个例子:

class Program
{
    static void Main(string[] args)
    {
        Start();

        GC.Collect();
        Read();
    }

    static void Start()
    {
        Foo f = new Foo();
        System.Threading.Thread.Sleep(5_000);
    }
}

public class Foo
{
    System.Timers.Timer _timer;

    public Foo()
    {
        _timer = new System.Timers.Timer(1000);
        _timer.Elapsed += timer_Elapsed;
        _timer.Start();
    }

    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        WriteLine("System.Timers.Timer Elapsed.");
    }
    
    ~Foo()
    {
        WriteLine("---------- End ----------");
    }
}

运行结果如下:

System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
...

Start 方法结束后,Foo 实例已经失去了作用域,按理说应该被回收,但实际并没有(因为析构函数没有执行,所以肯定实例未被回收)。

这就是定时器的 保活机制,因为定时器需要执行 timer_Elapsed 方法,而该方法属于 Foo 实例,所以 Foo 实例被保活了。

但多数时候这并不是我们想要的结果,这种结果导致的结果就是 内存泄露,解决方案是:先将定时器 Dispose

public class Foo : IDisposable
{
    ...
    public void Dispose()
    {
        _timer.Dispose();
    }
}

一个很好的准则是:如果类中的任何字段所赋的对象实现了IDisposable 接口,那么该类也应当实现 IDisposable 接口。

在这个例子中,不止 Dispose 方法,Stop 方法和设置 AutoReset = false,都能起到释放对象的目的。但是如果在 Stop 方法之后又调用了 Start 方法,那么对象依然会被保活,即便 Stop 之后进行强制垃圾回收,也无法回收对象。

System.Timers.TimerSystem.Threading.Timer 的保活机制是类似的。

保活机制是由于定时器引用了实例中的方法,那么,如果定时器不引用实例中的方法呢?

2、不保活下 System.Timers.TimerSystem.Threading.Timer 的差异

要消除定时器对实例方法的引用也很简单,将 timer_Elapsed 方法改成 静态 的就好了。(静态方法属于类而非实例。)

改成静态方法后再次运行示例,结果如下:

System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
---------- End ----------
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
...

Foo 实例是被销毁了(析构函数已运行,打印出了 End),但定时器还在执行,这是为什么呢?

这是因为,.NET Framework 会确保 System.Timers.Timer 的存活,即便其所属实例已经被销毁回收。

如果改成 System.Threading.Timer,又会如何?

class Program
{
    static void Main(string[] args)
    {
        Start();

        GC.Collect();
        Read();
    }

    static void Start()
    {
        Foo2 f2 = new Foo2();
        System.Threading.Thread.Sleep(5_000);
    }
}

public class Foo2
{
    System.Threading.Timer _timer;

    public Foo2()
    {
        _timer = new System.Threading.Timer(timerTick, null, 0, 1000);
    }

    static void timerTick(object state)
    {
        WriteLine("System.Threading.Timer Elapsed.");
    }

    ~Foo2()
    {
        WriteLine("---------- End ----------");
    }
}

注意,这里的 timerTick 方法是静态的。运行结果如下:

System.Threading.Timer Elapsed.
System.Threading.Timer Elapsed.
System.Threading.Timer Elapsed.
System.Threading.Timer Elapsed.
System.Threading.Timer Elapsed.
---------- End ----------

可见,随着 Foo2 实例销毁,_timer 也自动停止并销毁了。

这是因为,.NET Framework 不会保存激活 System.Threading.Timer 的引用,而是直接引用回调委托。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在uniapp中,可以使用lqm-keepalive插件实现定时器保活功能。首先,需要引入lqm-keepalive插件和globalEvent插件。然后,在需要保活的业务逻辑之前,调用lqmkeepalive.start函数来启动定时器保活。该函数接受一个包含相关参数的对象作为参数,其中可以设置定时器的标题、大标题、详细内容以及延迟时间。当延迟时间为正数时,每隔指定的毫秒数,定时器回调函数会被调用一次。建议在回调函数中进行业务逻辑的处理,例如接口测试、数据打印以及稳定性测试等。启动成功后,服务会在通知栏中显示出来。 示例代码如下: const lqmkeepalive = uni.requireNativePlugin('lqm-keepalive'); var globalEvent = uni.requireNativePlugin('globalEvent'); // 在需要保活的业务逻辑之前 lqmkeepalive.start({ title: "我正在每5秒刷新一次日志", big_title: '我正在刷新日志', content: "不要关闭我,否则我就没法刷新了", delaysec: 5000 }, result => { // 业务逻辑处理 // ... }); 通过以上代码,可以实现uniapp中的定时器保活功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C# 定时器保活机制引起内存泄露问题解决](https://download.csdn.net/download/weixin_38688403/13739472)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Uniapp-APP后台保活插件(安卓后台保活)](https://blog.csdn.net/weixin_57844432/article/details/128947263)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值