BeginInvokeOnMainThread方法是否可以循环并导致内存泄漏?

时间:2017-08-06 09:25:26

标签: c# xamarin xamarin.forms

我有一个可以正常运行的应用程序,但是一段时间后我在我的iPhone上进行调试它挂起了手机,我唯一可以恢复的方法是对侧面按钮和主页按钮进行硬重置。

首先,可能是因为我的应用程序有内存泄漏?

这是应用程序的代码。特别是,我正在研究BeginInvokeOnMainThread方法。有人能告诉我他们是否可以看到它的实施方式是否存在任何问题?另外,.ContinueWith((arg)的目的是什么。

namespace Japanese
{
    public partial class PhrasesFrame : Frame
    {

        CancellationTokenSource cts = new CancellationTokenSource();

        public PhrasesFrame(PhrasesPage phrasesPage)
        {
            InitializeComponent();
            this.phrasesPage = phrasesPage;
            AS.phrasesFrame = this;
            Device.BeginInvokeOnMainThread(() => ShowCards(cts.Token).ContinueWith((arg) => { }));
        }

        public void Disappearing()
        {
            cts.Cancel();
        }

        public async Task ShowCards(CancellationToken ct)
        {
            AS.cardCountForSelectedCategories = App.DB.GetCardCountForSelectedCategories();
            while (!ct.IsCancellationRequested)
            {

                await Task.Delay(500);
            }
        }
    }
}

1 个答案:

答案 0 :(得分:7)

ContinueWith

首先,让我们解答有关.ContinueWith((arg) => { }))的问题。 ContinueWith告知更多代码在原始Task完成后执行。在我们的示例中,ContinueWith内的代码将在Device.BeginInvokeOnMainThread(() => ShowCards(cts.Token)完成后运行。

在这种情况下,ContinueWith内没有代码,因此我们可以将其删除。

冷冻

是的,我可以看到此代码有可能冻结UI。

BeginInvokeOnMainThread会将Action排队以在主线程(也称为UI线程)上运行。主线程一直在监听用户输入(点击屏幕上的按钮,捏合缩放等),如果此线程忙于执行长时间运行的任务,它将无法响应用户和#39;输入直到完成;因此你的应用程序将显示为冻结。

主线程正在调用代码await Task.Delay(500);。因此,我们告诉主线程冻结自己500毫秒,并无限期地循环。

一种解决方案是将此代码包装在Task.Run中,这将把它放在后台线程中,并释放主线程来监听/响应用户输入。

Task.Run(async () => 
{ 
    while (!ct.IsCancellationRequested)
    {
          await Task.Delay(500);
    }
}

更多线程化建议

  • 仅在需要更新UI时使用BeginInvokeOnMainThread。 99%的代码可以在后台线程上运行而没有任何问题。然而,1%是更新UI的代码;任何更新UI 的代码必须在主线程上运行。

  • 如果执行的任务花费的时间超过了屏幕的刷新率,请在后台线程上执行。例如,如果屏幕的刷新率为60Hz,则每16.7ms更新60次/秒。因此,如果我们有一段需要20ms才能执行的代码块,我们需要在后台线程上执行它,以确保我们不会冻结应用程序并丢弃任何帧。

    • 上面的代码看起来像是在访问数据库,我强烈建议你移植到这样的后台线程
await Task.Run(() => AS.cardCountForSelectedCategories = App.DB.GetCardCountForSelectedCategories());