0

I have an application with a ListView that is generated on Load with times and files to play at those times. I have the following code working on a test machine, but on my development PC (while running from VS) I get a cross-thread error.

My questions are:

1: Any thoughts on why this would this give an InvalidOperationException on the development PC (from VS only, it works fine from the .exe)? Should these subs both have delegates? The error I get from both subs is "Cross-thread operation not valid: Control 'lstSchedule' accessed from a thread other than the thread it was created on."

2: Any suggestions of a better way of using timers run from ListView times other than what I'm doing? The timer here is a System.Threading.Timer.

Public Sub SetNextFileToPlay()

    Try
        Dim lviTemp As ListViewItem = Me.lstSchedule.Items(IndexToPlayNext)
        Dim tcb As TimerCallback = Sub()
                                       'Play the audio file when the timer elapses.
                                       objAudio.PlayMP3(lviTemp)
                                       'After the file plays, set the next timer.
                                       SetNextFileToPlay()
                                   End Sub

        'Set the play time 10 minutes ahead, in case we are unable to parse a time from the listview.
        Dim dtPlay As DateTime = DateTime.Now.AddMinutes(10)
        'Try to parse a time from the listview.
        DateTime.TryParse(lviTemp.SubItems(1).Text, dtPlay)
        'Ensure that the time is for today so we don't end up with a negative timespan.
        dtPlay = New DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, dtPlay.Hour, dtPlay.Minute, dtPlay.Second)

        'Subtract the current date/time from the playlist date/time.
        Dim execTime As TimeSpan = dtPlay.Subtract(DateTime.Now)

        'Create the timer.
        tmrMP3 = New Timer(tcb, Nothing, execTime, TimeSpan.Zero)
    Catch ex As Exception
        oLog.Error("Error playing the file: " & ex.Message)
    Finally
        IndexToPlayNext += 1
    End Try

End Sub


Public Sub PlayMP3(ByRef ListViewItem As ListViewItem)

    Try
        'Mute the Line In device.
        Me.MuteMusic(True)

        'Set the file to play.
        Dim strFileName As String = String.Empty

        Try
            strFileName = AUDIO_MP3_PATH & ListViewItem.SubItems(4).Text
            If Not File.Exists(strFileName) Then strFileName = AUDIO_MP3_BACKUP_PATH & "blank.wma"
        Catch ex As Exception
            strFileName = AUDIO_MP3_BACKUP_PATH & "blank.wma"
        End Try

        'Create a new audio file reader object, so we don't have two files playing over each other.
        afrMP3 = Nothing
        afrMP3 = New AudioFileReader(strFileName)

        wavMP3.Stop()
        wavMP3 = Nothing
        wavMP3 = New WaveOut
        wavMP3.Init(afrMP3)
        wavMP3.Play()

        'Set the text for the ListViewItem status.
        SetListViewItemText(ListViewItem.ListView, ListViewItem.Index, "Playing")

        'Break the ListViewItem down into a ListView and index, since you cannot use Invoke on as ListViewItem.
        Dim tmpListView As ListView = ListViewItem.ListView
        Dim tmpListViewIndex As Int32 = ListViewItem.Index

        'When the file stops, unmute the Line In device and set the text for the ListViewItem status.
        AddHandler wavMP3.PlaybackStopped, Sub(sender, e) MuteMusic(False)
        AddHandler wavMP3.PlaybackStopped, Sub(sender, e) SetListViewItemText(tmpListView, tmpListViewIndex, "Played")

    Catch ex As Exception

        oLog.Error(ex.Message)

    End Try

End Sub
Capellan
  • 717
  • 6
  • 15
  • What line does it happen on? – djv Sep 08 '14 at 18:26
  • @DanVerdolino It was giving First Chance Exceptions so I didn't even see which lines it happened on. First it happens on 'SetListViewItemText(ListViewItem.ListView, ListViewItem.Index, "Playing")' in PlayMP3 and then again at 'Dim lviTemp As ListViewItem = Me.lstSchedule.Items(IndexToPlayNext)' when SetFileToPlayNext is called by the TimerCallBack. So I placed checks to see if InvokeRequired for the ListView in both subs, which I think would work. The problem now is that when SetFileToPlayNext is called, InvokeRequired is false, but when TimerCallBack is triggered, InvokeRequired is true. – Capellan Sep 08 '14 at 18:49
  • I tried adding a second InvokeRequired check into the lambda expression with 'Me.lstSchedule.Invoke(New clsAudio.PlayMP3Invoker(Sub() objAudio.PlayMP3(Me.lstSchedule, lviTemp.Index)))' (I changed what was passed into PlayMP3), but I get ParameterCountException when it tries to call that. – Capellan Sep 08 '14 at 18:56
  • You should be able to simplify it as `Me.lstSchedule.Invoke(Sub() objAudio.PlayMP3(Me.lstSchedule, lviTemp.Index))` – djv Sep 08 '14 at 19:02
  • But you need to understand and think about the design. Invoking on the UI thread means the code runs there, and nothing else can run there i.e. it's a blocking call. So your UI will be unresponsive for the duration of the invokation. So you want to invoke the minimum amount of code possible - not playing the entire song. Think about limiting your invokation to retrieving or setting properties of the control. If you are stuck and *need* to use the ListView a lot, think about moving your information to a non-UI data structure as early in the code as possible. – djv Sep 08 '14 at 19:05
  • @DanVerdolino I can look at the design some more, but the mp3 playing is done asynchronously with NAudio so I don't notice any issues with the UI. There will be very little user interaction with this PC in either case. Thanks for the feedback. – Capellan Sep 08 '14 at 19:28

0 Answers0