Debugging parallel threads (solving the delete rows problem)

This blog needs some programming experience.

Lc is developed for several years, but as each program it also has some hidden bugs. One of the basic functionality is to delete transactions. You do it rarely, so it is an ideal place for a bug to hide. At row deletion sometimes there was an exception, not all the selected rows were deleted, or the program crashed. But other times there was no problem at all, so I focused on more pressing issues.

Now I started to figure out the problem. I have a method CopyDataFromTableVmToDbAsync which compares the actual view model, the rows in the table, to the backup. If there is any change then the database is updated. After that the view is refreshed; deleting a row will change the USD balance of the subsequent rows. At the refresh the view model backup is also refreshed.



I have put a breakpoint to the first row of CopyDataFromTableVmToDbAsync. You can see Debug info there and after that comes a condition which wants to prevent that two instances of the this method run parallel. It is a previous attempt to handle the delete row problem. 



The call stack at the breakpoint is strange. The method is called by some external code (not code I have written).


A normal call stack shows how I got to the current method. I did not remember how I called CopyDataFromTableVmToDbAsync  and it was not easy to figure out from the code. 



If I go to the Parallel stacks window I see this. 



If I turn on "Show external code" then I see a more detailed view. The main thing is that I am in a Worker thread and not in the Main thread.



During debug sometimes an exclamation mark appears at the yellow arrow, indicating that "The process or thread has changed since the last step". Both the worker thread and the exclamation mark indicates parallel execution so I checked if I used Task.Run(CopyDataFromTableVmToDbAsync) and I found the property setter of the SelectedRow. If the SelectedRow changes I check if rows are deleted and if it is the case I use Task.Run(CopyDataFromTableVmToDbAsync) which starts a parallel task and go on with the execution of the property setter without waiting for the finish of the CopyDataFromTableVmToDbAsync method. One can not use await in a property setter. 


When rows are deleted the setter of the SelectedRow fires twice. On the image you can see that the double fire resulted the same method is running twice. I try to prevent running twice CopyDataFromTableVmToDbAsync with the IsCopyDataFromTableVmToDbInProgress flag. I set its value to true at the beginning of the corresponding method and to false at the end of that method. Unfortunately, if the timing is unlucky then it is still possible that  two instances of CopyDataFromTableVmToDbAsync start before the flag is set to true. I could improve (but not solve) this situtation by checking the flag in the the setter of the SelectedRow also.



There is an additional problem. Row 636 did not modify DisplayedRows, it only selected the identifiers into a new variable. Still you can see in the watch window that DisplayedRows has changed. The count was 6 and now it is 5. The red color indicates the change. So while the CopyDataFromTableVmToDbAsync method is executed some other thread changes this variable. The effect of the delete happens in the background gradually. This is really bad. The setter of the SelectedRow is not a good place to handle row deletion because the view model may not fully updated at this time. 

The idea is to introduce a new button to delete rows as it can be seen above. 


    private async Task DeleteRowsAsync(object obj)
    {
        if (IsErrorMethod())
            return;
        var selectedIds = SelectedItems.Cast<IHasId>().Select(e => e.Id).ToList();
        if (selectedIds.Count == 0)
        {
            MessageBox.Show(Translator["No item is selected."]);
            return;
        }
        foreach (var selectedId in selectedIds)
            DisplayedRows.RemoveAll(e => e.Id == selectedId);
        await CopyDataFromTableVmToDbAsync();
    }

Pressing this button will execute the command which performs the method above. I check if there is any validation error, check if no item is selected, then the selected rows are removed from DisplayedRows. At end I can call CopyDataFromTableVmToDbAsync using the await keyword. I also make sure that the button is disabled while the command is executing.


If you debug now there will be one instance of CopyDataFromTableVmToDbAsync and it is in the main thread. Moreover there will be no thread change during the debug.

The xaml above shows that the CanUserDeleteRows property is false now and I added InputBinding to the new command so the delete key still works.

Figuring out the problem was not easy. I went into a lot of dead end. For example, I assumed there is some caching problem with LocalDb. There are still some improvements to be made. For example, I remove the rows one by one in a loop which triggers OnCollectionChanged event in each iteration. It is more efficient to suppress this event and trigger one event at the end.













Previous Post Next Post

Contact Form