【问题标题】:Memory Leak caused by Task in ViewModelViewModel中的Task导致的内存泄漏
【发布时间】:2019-09-05 19:25:24
【问题描述】:

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

问题是任务,当我删除它时,一切都很好,并且 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");
    }
}

【问题讨论】:

  • 您是否考虑过改用Timer
  • 是的,我尝试了一个计时器。它有完全相同的问题。我知道这段特定的代码会导致内存泄漏,因为我使用 dotMemory 对其进行了分析,并且还有一个多次实例化 View 的测试。除非删除任务/计时器,否则内存会不断增长。
  • 您正在捕获隐含在 lambda 中的 ViewModel (this) 实例。尝试改用 Wea​​kRefrence。
  • 现在包含在内。我真的不明白为什么两者都不起作用。
  • 泄漏有多大(多长时间内有多少内存)?

标签: c# task viewmodel


【解决方案1】:

我找到了解决方案。感谢 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

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

【讨论】:

    【解决方案2】:

    您的 Dispose 任务似乎永远不会返回,这就是您的对象保留在内存中的原因。我将问题追踪到了

    await Task.Delay(1000)
    

    如果您根据这篇帖子 https://stackoverflow.com/a/24539937/3084003 更改它,它将起作用

    await Task.Delay(1000).ConfigureAwait(false);
    

    【讨论】:

    • 您好,谢谢您的建议。不幸的是,这对我没有用。我想我会再花一天的时间来研究这个问题。
    猜你喜欢
    • 1970-01-01
    • 2012-11-20
    • 2014-04-19
    • 1970-01-01
    • 1970-01-01
    • 2015-07-06
    • 2014-06-07
    • 2013-11-20
    • 2011-10-28
    相关资源
    最近更新 更多