首页 > 解决方案 > ViewModel中的Task导致的内存泄漏

问题描述

我有以下代码,它会导致内存泄漏。

问题是任务,当我删除它时,一切都很好,并且 View 以及 ViewModel 都被 GCed。似乎任务保留了对 UpdateTimeDate 的引用,因此也保留了 ViewModel。我尝试了各种方法,但都没有奏效,希望有人有任何想法或解释为什么会这样。

public class HeaderViewModel : Observable, IDisposableAsync
{
    public HeaderViewModel (IDateTimeProvider dateTimeProvider)
    {
        TokenSource = new CancellationTokenSource();

        ATask = Task.Run(
            async () =>
            {
                while(!TokenSource.Token.IsCancellationRequested)
                {
                    UpdateTimeData();
                    await Task.Delay(800);
                }

                IsDisposed = true;
            },
            TokenSource.Token);

        UpdateTimeData();

        void UpdateTimeData()
        {
            TimeText = dateTimeProvider.Now.ToString("HH:mm:ss");
            DateText = dateTimeProvider.Now.ToString("dd.MM.yyyy");
        }
    }

    public CancellationTokenSource TokenSource { get; set; }

    public bool IsDisposed { get; set; }

    public string TimeText
    {
        get => Get<string>();
        set => Set(value);
    }

    public string DateText
    {
        get => Get<string>();
        set => Set(value);
    }

    private Task ATask { get; set; }

    public async Task Dispose()
    {
        TokenSource.Cancel();

        while(!IsDisposed)
        {
            await Task.Delay(50);
        }

        TokenSource.Dispose();
        ATask.Dispose();
        ATask = null;
        TokenSource = null;
    }
}

这是基于 Timer 的解决方案,它也会导致内存泄漏:

public class HeaderViewModel : Observable, IDisposableAsync
{
    public HeaderViewModel(IDateTimeProvider dateTimeProvider)
    {
        DateTimeProvider = dateTimeProvider;

        ATimer = new Timer(800)
        {
            Enabled = true
        };

        UpdateTimeData(this, null);

        ATimer.Elapsed += UpdateTimeData;
    }

    public string TimeText
    {
        get => Get<string>();
        set => Set(value);
    }

    public string DateText
    {
        get => Get<string>();
        set => Set(value);
    }

    public bool IsDisposed { get; set; }

    private IDateTimeProvider DateTimeProvider { get; }

    private Timer ATimer { get; }

    public async Task Dispose()
    {
        ATimer.Stop();

        await Task.Delay(1000);

        ATimer.Elapsed -= UpdateTimeData;
        ATimer.Dispose();
        IsDisposed = true;
    }

    private void UpdateTimeData(object sender, ElapsedEventArgs elapsedEventArgs)
    {
        TimeText = DateTimeProvider.Now.ToString("HH:mm:ss");
        DateText = DateTimeProvider.Now.ToString("dd.MM.yyyy");
    }
}

标签: c#taskviewmodel

解决方案


我找到了解决方案。感谢 keuleJ,他发布了引导我的评论。因此,当您创建其中任何一个时,Task 或 Timer 正在捕获 ViewModel 的实例。防止它的方法是创建一个 WeakReference 并使用它:

public class HeaderViewModel : Observable, IDisposableAsync
{
    public HeaderViewModel(IDateTimeProvider dateTimeProvider)
    {
        DateTimeProvider = dateTimeProvider;

        UpdateTimeData();

        var weakReference = new WeakReference(this);

        Task.Run(
            async () =>
            {
                while(!((HeaderViewModel)weakReference.Target).IsDisposing)
                {
                    ((HeaderViewModel)weakReference.Target).UpdateTimeData();
                    await Task.Delay(800);
                }

                ((HeaderViewModel)weakReference.Target).IsDisposed = true;
            });
    }

    public bool IsDisposed { get; set; }

    public string TimeText
    {
        get => Get<string>();
        set => Set(value);
    }

    public string DateText
    {
        get => Get<string>();
        set => Set(value);
    }

    private IDateTimeProvider DateTimeProvider { get; }

    private bool IsDisposing { get; set; }

    public async Task Dispose()
    {
        IsDisposing = true;

        while(!IsDisposed)
        {
            await Task.Delay(50);
        }
    }

    private void UpdateTimeData()
    {
        TimeText = DateTimeProvider.Now.ToString("HH:mm:ss");
        DateText = DateTimeProvider.Now.ToString("dd.MM.yyyy");
    }
}

请注意,我也无法从

(HeaderViewModel)weakReference.Target

一旦我这样做了,一些魔法似乎发生了,实例将再次被捕获。


推荐阅读