0

This code below throws a NullReferenceException error time to time. It does not happen always but let's say, at least 2-3 times in 10 tries I get this annoying "System.NullReferenceException" screen.

I'm reading data from a data acquisition card, DATAQ 4208U. When it comes to "stop" command for reading, this error occurs. And the other problem is I am not a master at coding and VB.Net.

the point where it throws the error is at the end, (sure I just quoted the code, it doesnt end there)

Await TargetDevice.ReadDataAsync(cancelRead.Token)

 Private Async Sub btnState_Click(sender As Object, e As EventArgs) Handles btnState.Click
    If cancelRead IsNot Nothing Then

        'Get here if an acquisition process is in progress and we've been commanded to stop
        cancelRead.Cancel() 'cancel the read process
        cancelRead = Nothing
        Await taskRead 'wait for the read process to complete
        taskRead = Nothing
        Await TargetDevice.AcquisitionStopAsync() 'stop the device from acquiring 

    Else
        'get here if we're starting a new acquisition process
        TargetDevice.Channels.Clear() 'initialize the device
        ConfigureAnalogChannels()
        ConfigureDigitalChannels()

        If SampleRateBad() Then
            'get here if requested sample rate is out of range
            'It's a bust, so...
            btnState.Enabled = True
            Exit Sub
        End If
        'otherwise, the selected sample rate is good, so use it. The class automatically adjusts
        'decimation factor and the protocol's sample rate denominator to yield a sample rate value as close as possible to
        'the value asked for in tbSampleRate.Text. The class also automatically maximizes decimation factor as a function of 
        'channels' AcquisitionMode settings. For this reason Acquisition mode should be defined for all enabled channels
        'before defining sample rate. 
        TargetDevice.SetSampleRateOnChannels(tbSampleRate.Text)

        Try
            Await TargetDevice.InitializeAsync() 'configure the device as defined. Errors if no channels are enabled
        Catch ex As Exception
            'Detect if no channels are enabled, and bail if so. 
            MessageBox.Show("No enabled analog or digital channels.",
                            "Configuration Problem", MessageBoxButtons.OK, MessageBoxIcon.Error)
            btnState.Enabled = True
            Exit Sub
        End Try

        'now determine what sample rate per channel the device is using from the 
        'first enabled input channel, and display it
        Dim FirstInChannel As Dataq.Devices.DI4208.ChannelIn
        Dim NoInputChannels As Boolean = True
        For index = 0 To TargetDevice.Channels.Count - 1
            If TypeOf TargetDevice.Channels(index) Is Dataq.Devices.IChannelIn Then
                FirstInChannel = TargetDevice.Channels(index)
                lblDecimation.Text = FirstInChannel.AcquisitionMode.Samples
                NoInputChannels = False
                Exit For
            End If
        Next
        If NoInputChannels Then
            MessageBox.Show("Please configure at least one analog channel or digital port as an input",
                            "No Inputs Enabled", MessageBoxButtons.OK, MessageBoxIcon.Error)
            btnState.Enabled = True
            Exit Sub
        End If
        'Everything is good, so...
        btnState.Text = "Stop" 'change button text to "Stop" from "Start"
        cancelRead = New CancellationTokenSource() ' Create the cancellation token
        Await TargetDevice.AcquisitionStartAsync() 'start acquiring

        ' NOTE: assumes at least one input channel enabled
        ' Start a task in the background to read data
        taskRead = Task.Run(Async Function()
                                'capture the first channel programmed as an input (MasterChannel)
                                'and use it to track data availability for all input channels
                                Dim MasterChannel As Dataq.Devices.IChannelIn = Nothing
                                For index = 0 To TargetDevice.Channels.Count
                                    If TypeOf TargetDevice.Channels(index) Is Dataq.Devices.IChannelIn Then
                                        MasterChannel = TargetDevice.Channels(index) ' we have our channel 
                                        Exit For
                                    End If
                                Next

                                ' Keep reading while acquiring data
                                While TargetDevice.IsAcquiring
                                    ' Read data and catch if cancelled (to exit loop and continue)
                                    Try
                                        'throws an error if acquisition has been cancelled
                                        'otherwise refreshes the buffer DataIn with new data
                                        'ReadDataAsync moves data from a small, temp buffer between USB hadrware and Windows
                                        'into the SDK's DataIn buffer. ReadDataAsync should be called frequently to prevent a buffer
                                        'overflow at the hardware level. However, buffer DataIn can grow to the size of available RAM if necessary.
                                        Await TargetDevice.ReadDataAsync(cancelRead.Token)
                                    Catch ex As OperationCanceledException
                                        'get here if acquisition cancelled
                                        Exit While
                                    End Try
isilter
  • 3
  • 1
  • Could you include a stack trace? – ZeroWorks Mar 29 '21 at 06:28
  • Does this answer your question? [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Panagiotis Kanavos Mar 29 '21 at 06:30
  • `await` doesn't throw NREs. Only trying to use a null variable or field does this. It's almost always a problem in the code - using a variable or parameter without checking it, or forgetting to check the return value of a method. Ignoring an exception like this `Catch ex As OperationCanceledException` is a *great* way to cause NREs at best, or keep working with bad data at worst. Millions of dollars have been lost due to such bad code - there's no sugar-coating this – Panagiotis Kanavos Mar 29 '21 at 06:32
  • 1
    If you work with hardware, ignoring errors can easily result to actual physical damage or injury. Fix the problem instead of trying to cover it up – Panagiotis Kanavos Mar 29 '21 at 06:34
  • More problems with this method - `Task.Run` is never awaited, so it probably doesn't even start before the event handler exits. By that time, `TargetDevice` may be `null`. There's no reason to use `Task.Run` to execute an asynchronous method like `Await TargetDevice.InitializeAsync()`. This can run *after* whatever `Task.Run` does, assuming it really needs to do anything in the background. `Catch ex As OperationCanceledException` catches and *hides* a cancellation and keeps looping. Why? What's the point of `ReadDataAsync(cancelRead.Token)` then? – Panagiotis Kanavos Mar 29 '21 at 06:40
  • @PanagiotisKanavos thanks for all your replies. I read the thread you suggested but still I don't know what to do. I am an amateur and need an advice to stop taking this error because it cause me to loose all data coming from DAQ. I'm trying different methods but still couldn't find a way. Especially this part of the code doesn't belong to me. This code package came with DAQ as an example interface and I'm tryin to modify it for my needs – isilter Mar 29 '21 at 07:22
  • Do what the *answer* says. SO is a Q&A site, not a discussion forum. Debug your code and inspect the variables at the point the exception is thrown. At `Await TargetDevice.ReadDataAsync(cancelRead.Token)` the code calls two things that may be null - `TargetDevice` and `cancelRead`. Put a breakpoint on this line and check both variables. One of them is null – Panagiotis Kanavos Mar 29 '21 at 08:02
  • @PanagiotisKanavos `Task.Run` will immediately start up the routine on a thread pool thread and return a `Task` representing the ongoing work. As with anything involving `Async` and `Await`, it doesn't have to be `Await`ed in order to start. – Craig Mar 29 '21 at 13:36
  • @PanagiotisKanavos Also see my answer, I think your comments are more likely to mislead than help. The overall structure of the code is fine. – Craig Mar 29 '21 at 13:48
  • @Craig I never said it does. The time it actually starts executing though isn't certain. Unless `Task.Run` is awaited, the method will exit. There's no guarantee that `TargetDevice` will not be null when the delegate executes. That's a *very* common bug, typically encountered in web applications. – Panagiotis Kanavos Mar 29 '21 at 13:48
  • @PanagiotisKanavos No, that's not correct. It most assuredly will *not* exit. – Craig Mar 29 '21 at 13:49
  • @Craig using `await` inside a `Task.Run` inside an async method isn't fine. Putting a lot of business logic in a button handler isn't fine either. At the very least, the code should be readable – Panagiotis Kanavos Mar 29 '21 at 13:50
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230505/discussion-between-craig-and-panagiotis-kanavos). – Craig Mar 29 '21 at 13:50

1 Answers1

0

Your problem is with this sequence:

    cancelRead.Cancel() 'cancel the read process
    cancelRead = Nothing
    Await taskRead 'wait for the read process to complete

This will work if, at the moment you call cancelRead.Cancel(), the task is currently on the Await TargetDevice.ReadDataAsync step.

The problem occurs when it is not on that step. Calling Cancel on cancelRead doesn't do anything magical, and in particular, it doesn't spontaneously issue an OperationCanceledException in the task. It puts the token in the canceled state, so the OperationCanceledException will issue the next time it's accessed... but then the task tries to do the next iteration of Await TargetDevice.ReadDataAsync and pops a NullRefException because you try to get Token from a null cancelRead.

If you swap the setting of cancelRead to nothing with Await taskRead to wait for the process to complete, it should resolve the issue.

One other note: Task implements IDisposable, so you should call Dispose on taskRead before setting it to Nothing, otherwise there may be a risk of leaking resources. Similarly for cancelRead, CancellationTokenSource also implements IDisposable.

Craig
  • 2,248
  • 1
  • 19
  • 23