android 如何避免Task.Delay(在Timer的Elapsed方法中)第二次抛出NullReferenceException?

0md85ypi  于 2023-06-20  发布在  Android
关注(0)|答案(1)|浏览(139)

我正在尝试使用. Net毛伊岛应用程序中的计时器拍照。我有图片工作得很好,可以得到5 - 10张图片的第一次运行的计时器,但不是在第二次调用计时器方法。多次运行"StartTimer"方法(在下面的代码中)会抛出"System.NullReferenceException:'Object reference not set to an instance of an object.'"在Task.Delay调用或System.Timers.Timer事件的后续触发上。这会导致应用程序崩溃,即使我有try...catch块。
我也试过使用System.Threading.Timer,但它从来没有触发。
我知道why发生了异常。算是吧即使我没有包含CancellationToken或者我创建了一个新的token,它仍然会抛出错误。
我使用Camera.Maui NuGet插件的相机功能,如果这件事。如果我能让AutoSnapShot功能工作,我可能不需要在这里得到答案,但那就是a different question
这是一个针对Android API 32的. Net 7 Maui应用程序。到目前为止,我只在Android中测试过它,但我已经在运行API版本28、31和33的真实设备上在ReleaseDebug模式下测试过它。唯一的区别是API 28电话在第二次运行时拍摄1张照片,然后抛出异常,然后拍摄另一张照片。此外,异常不会使应用程序崩溃。这允许我尝试多次运行该方法,但每次都有与第二次运行相同的问题。
那么,我做错了什么和/或我如何避免NullReferenceException

代码

问题代码在它自己的类中。我从MainPage.xaml.cs中的异步方法调用它。即使我为“timer”变量创建了一个新的类示例,它仍然会抛出NullReferenceException。我还更改了类以实现IDisposable,以尝试清除任何残留的...不管是什么可能影响类或方法的重用,它仍然会抛出异常。
编辑:我已经做了一些更多的测试,在实现IDisposable和不实现IDisposable的情况下,“timer”变量是本地的,并且在实现IDisposable时不使用using语句,并且当多次运行“StartTimer”时,所有测试仍然抛出NullReferenceException

private readonly TimerTrigger timer = new();

    private async Task<bool> StartCapture()
    {
        try 
        {
            ...
            /*
            using TimerTrigger timer = new();
            */
            timer.StartTimer(quantity, timerDelay);
            ...
        }
        catch
        {
            return false;
        }

        return true;
    }

版本1,带有Task.Delay

public async void StartTimer(int quantity, int timerDelay)
    {
        try
        {
            CancellationTokenSource wtoken = new();
            for (int i = 0; i < quantity; i++)
            {
                if (i > 0)
                {
                    try
                    {
                        // It doesn't matter if I use a token or not, the exception is thrown
                        // await Task.Delay(timerDelay);
                        // await Task.Delay(timerDelay, CancellationToken.None);
                        await Task.Delay(timerDelay, wtoken.Token);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message); // This is only hit in API 28
                    }
                }

                // Take picture
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message); // This is never hit
        }
    }

版本2,带有System.Timers.Timer

private System.Timers.Timer aTimer;
private int totalQuantity = 0;
private int totalTaken = 0;

    public void StartTimer(int quantity, int timerDelay)
    {
        totalQuantity = quantity;
        aTimer = new()
        {
            Interval = timerDelay
        };

        aTimer.Elapsed += SnapPic;
        aTimer.AutoReset = true;
        aTimer.Enabled = true;
    }

    private void SnapPic(object sender, ElapsedEventArgs e)
    {
        try
        {
            // Take picture
            totalTaken++;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message); // This is never hit
            EndCapture(false);
        }

        EndCapture(true);
    }
    
    private void EndCapture(bool success)
    {
        if (!success || totalTaken >= totalQuantity)
        {
            aTimer.AutoReset = false;
            aTimer.Enabled = false;
        }
    }

版本3,使用System.Threading.Timer

private int totalQuantity = 0;
private int totalTaken = 0;

    public void StartTimer(int quantity, int timerDelay)
    {
        AutoResetEvent autoEvent = new(true);
        totalQuantity = quantity;
        using Timer bTimer = new(SnapPic, autoEvent, 0, timerDelay);
        // using Timer bTimer = new(SnapPic, null, 0, timerDelay); // This doesn't run, either
    }

    private void SnapPic(object sender)
    {
        // This method is apparently never run
        try
        {
            // Take picture
            totalTaken++;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            EndCapture(false); // Reusing this method from Version 2
        }

        EndCapture(true); // Reusing this method from Version 2
    }
smdncfj3

smdncfj31#

在阅读了更多关于计时器的内容并使用了另一个使用System.Timers.Timer的项目之后,我找到了一个解决方案。
在版本2中,我需要调用Start方法,而不是设置属性来启动计时器。我需要调用CloseDispose方法,而不是设置属性来结束计时器。
旧的和破碎的:

// Start timer
aTimer.AutoReset = true;
aTimer.Enabled = true;

// End timer
aTimer.AutoReset = false;
aTimer.Enabled = false;

新的和工作:

// Start timer, I mean what else would we do, right?
aTimer.Start();

// End timer, obviously... {facepalm}
aTimer.Close();
aTimer.Dispose();

我必须加上“totalTaken = 0;”到“EndCapture”方法,如果有人关心的话。不重置这个变量使我认为当把“timer”作为“MainPage”类的属性放回去时,问题仍然会发生。
现在我只需要重命名“aTimer”变量。我已经剥离了一堆不工作的测试代码和版本1和3。

相关问题