0

I have a process that works well when it's running against small files but gives an "Message=Managed Debugging Assistant 'ContextSwitchDeadlock' : 'The CLR has been unable to transition from COM context 0xa5b8e0 to COM context 0xa5b828 for 60 seconds." error when running against large files. I'm pretty new to VB and did some investigating and found that using Application.DoEvent seemed to be recommended. I was hoping that someone could show me an example of how to use that. If I'm executing a Sub called "Process1", how would I use the DoEvent to prevent it from timing out. Ideally I'd like to add a progress bar as well but I have ot idea on that one either. I'd appreciate any help. Please keep it simple as I'm new to VB/VS.

This is the comment from my first question showing the code. Process1 calls a sub named ArchDtlCopyFile1 which scrolls through the values in a list view, copying the files named in the items to a different location. It then calls ArchDtlCheckCopy1 to scroll through the list view again to ensure that the copy was done. It then decides if the source file should be deleted and do it if required. Finally it inserts a row in an Access table documenting the change made.

Private Sub Process1()
    If ReturnCode = 0 Then
        ArchDtlCopyFile1()
    Else
        ' MessageBox.Show("Error code coming in is: " & CStr(ReturnCode))
    End If

    If ReturnCode = 0 Then
        ArchDtlCheckCopy1()
    Else
        '  MessageBox.Show("Error code for check copy is: " & CStr(ReturnCode))
    End If
End Sub

Private Sub ArchDtlCopyFile1()

    intLVIndex = 0

    ' Copy the file from the source computer onto the NAS
    Do While intLVIndex < intMaxFileIndex
        Try

            ' Select the row from the LVFiles ListView, then move the first column (0) into strSourceFilePath and the last
            ' column (3) into strDestFilePath. Execute the CopyFile method to copy the file.
            LVFiles.Items(intLVIndex).Selected = True
            strSourceFilePath = LVFiles.SelectedItems(intLVIndex).SubItems(0).Text
            strDestFilePath = LVFiles.SelectedItems(intLVIndex).SubItems(3).Text
            My.Computer.FileSystem.CopyFile(strSourceFilePath, strDestFilePath, overwrite:=False)

        Catch ex As Exception

            ' Even if there's an error with one file, we should continue trying to process the rest of the files
            Continue Do

        End Try

        intLVIndex += 1

    Loop

End Sub

Private Sub ArchDtlCheckCopy1()

    intLVIndex = 0
    intLVError = 0

    '    ' Check each file was copied onto the NAS
    Do While intLVIndex < intMaxFileIndex


        ' Select the row from the LVFiles ListView, then move the last column (3) into strDestFilePath.
        ' Use the FileExists method to ensure the file was created on the NAS. If it was, call the
        ' ADetDelete Sub to delete the source file from the user's computer.

        LVFiles.Items(intLVIndex).Selected = True
        strSourceFilePath = LVFiles.SelectedItems(intLVIndex).SubItems(0).Text
        strDestFilePath = LVFiles.SelectedItems(intLVIndex).SubItems(3).Text
        strSourceFile = LVFiles.SelectedItems(intLVIndex).SubItems(1).Text
        Try

            If My.Computer.FileSystem.FileExists(strDestFilePath) Then
                ' Archive file was created so go ahead and delete the source file
                'If strSourceFile = myCheckFile Then
                '    strDestFile = LVFiles.SelectedItems(intLVIndex).SubItems(3).Text
                'End If
                If RBArchive.Checked = True Then
                    ArchDtlDeleteFile(strSourceFilePath)
                End If
                PrepareDtlVariables()
                ADtlAddRow()

            Else
                MessageBox.Show("File not found. " & strDestFilePath)
            End If

        Catch ex As Exception

            ErrorCode = "ARC6"
            MessageCode = "Error while checking file copy"
            ReturnCode = 8

        End Try

        intLVIndex += 1
    Loop

End Sub
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
Ross from Brooklin
  • 293
  • 1
  • 5
  • 18
  • The context switch deadlock is more of a warning than an error. It's not necessary to fix it, if you're OK with your process being non-responsive for more than a minute. – Craig Jul 15 '20 at 19:05
  • 1
    If you're *not* OK with it, then DoEvents isn't likely to be the best way to fix it. The best way, if you can, is to get the processing off of the UI thread. In my opinion, the most straightforward way to do this is likely to be some combination of `Task.Run` and `Await` (keeping in mind that if the UI is responsive, you have to have a plan for how you handle re-entrancy and having a second operation initiated while the first is only half done). – Craig Jul 15 '20 at 19:10
  • As @Craig said, you need to move that process to a different thread. There are various ways to do that, though, while updating the user interface at the same time (you indicated you want a ProgressBar). Show us your code that launches `Process1()`, as well as the code inside `Process1()`, and we can help you out... – Idle_Mind Jul 15 '20 at 19:17
  • 3
    The Q&A [Use of Application.DoEvents()](https://stackoverflow.com/q/5181777/1115360) has answers which show why it is *not* recommended. – Andrew Morton Jul 15 '20 at 20:11
  • If your process is happening inside of a loop, please show your loop code and we can give you an answer. If your code doesn't have a loop (like if you call an API or function and it just hangs for a minute) then `DoEvents` won't help. – tgolisch Jul 15 '20 at 20:22
  • Note regarding the link from @AndrewMorton : the most significant pitfalls are not specific to DoEvents, but apply to any approach to keeping a responsive UI that would allow re-entrancy. Dealing with that is the price you pay for having a responsive UI. I've had some very entertaining re-entrancy bugs with async/await code with nary a DoEvents in sight. – Craig Jul 15 '20 at 20:57
  • Sorry but most of you are speaking a language that I don't really understand. I don't know how to add the extra code to my first question so I'll ask another with the same title that will have the Process1 code in it. This basically scrolls through a list view and for each item, copies the file to another directory, possibly then deleting the original file, and then inserting a row in an Access file to document the change made. – Ross from Brooklin Jul 15 '20 at 21:28
  • @RossfromBrooklin Please use the [edit] link which appears just under your question to add more information. – Andrew Morton Jul 15 '20 at 21:59
  • Does this answer your question? [How Do I Use VB Application.DoEvent](https://stackoverflow.com/questions/62924225/how-do-i-use-vb-application-doevent) – Caius Jard Jul 15 '20 at 22:01
  • I'd look at using the **BackgroundWorker** control. Put that long process in the `DoWork()` handler, and use `ReportProgress()` to pass out the current index so you show what item is currently being processed in the listview. – Idle_Mind Jul 16 '20 at 00:20
  • Craig - If I want the system to ignore the warning then and continue on, what do I need to do? Idle Mind - I like the BackgroundWorker option but am having a hard time getting it to work. Do you have any sample code I could look at? – Ross from Brooklin Jul 20 '20 at 15:00

1 Answers1

0

Here's a simplified example:

Imports System.ComponentModel
Imports System.IO
Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        BackgroundWorker1.WorkerReportsProgress = True
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Button1.Enabled = False
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        For i As Integer = 1 To 20
            BackgroundWorker1.ReportProgress(i) ' you can pass anything out using the other overloaded ReportProgress()
            System.Threading.Thread.Sleep(1000)
        Next
    End Sub

    Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        ' you can also use e.UserState to pass out ANYTHING
        Label1.Text = e.ProgressPercentage
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        MessageBox.Show("Done!")
        Button1.Enabled = True
    End Sub

End Class
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • I "almost" have it working now. The problem arises when the DoWork sub tries to read a ListView control item that was created during the real-time portion of the process. The error says that the thread is trying to access the ListView control outside the thread that it was created on. I could rebuild the LV in the DoWork, but I don't really want to. Is there a way around this? – Ross from Brooklin Jul 21 '20 at 14:21
  • See [this thread](https://stackoverflow.com/a/23096408/2330053) for an example of how to use Invoke() to READ from a different thread. – Idle_Mind Jul 21 '20 at 14:51