Too strange race condition, using the first approach (binding) to the ViewModel

I am experimenting with a simple WPF application based on ViewModel and some primitive navigation logic. The application consists of two views (screens). One screen has a Forward button and another has a Back button. By clicking one of the buttons, the delegate command is called, which, in turn, forces the view-model shell to switch the active screen. Screen 1 switches to Screen 2, while Screen 2 switches to Screen 1.

The problem with this approach is that it introduces a race condition. When pressed quickly enough, it becomes possible that the corresponding action (forward / backward) is performed twice, which leads to an application crash. The interesting thing is that the screen has already been changed, but the user interface does not reflect the state changes instantly. So far, I have never experienced such a gap, and I did this experiment only to prove that a single-threaded (sent) WPF application is automatically thread-safe.

Does anyone have an explanation for this strange behavior? Is the WPF binding mechanism too slow so the button can be pressed a second time until the user interface refreshes to represent the new state of the screen?

I do not know how to fix this in accordance with the recommendations for the development of mvvm applications. It is not possible to synchronize the code because there is only one thread. I hope you can help me, because now I feel very unsafe, relying on WPF data binding and template binding system.

Mail archive containing project files

Screen 1 switches to Screen 2, while Screen 2 switches to Screen 1. By pressing fast enough you can enter a race condition (this happens twice forward / backward).> </a> </p> <p> <strong> MainWindow.xaml : </STRONG> </P> <pre> <code> <Window x: Class =

ShellViewModel containing the "go forward" and "go back" method:

Public Class ShellViewModel
    Inherits PropertyChangedBase

    Private _currentScreen As Object
    Public Property Screens As Stack(Of Object) = New Stack(Of Object)()

    Public Sub New()
        Me.Screens.Push(New Screen1(Me))
        Me.GoForward()
    End Sub

    Property CurrentScreen As Object
        Get
            Return _currentScreen
        End Get
        Set(value)
            _currentScreen = value
            RaisePropertyChanged()
        End Set
    End Property

    Public Sub GoBack()
        Log("Going backward.")
        If (Me.Screens.Count > 2) Then
            Throw New InvalidOperationException("Race condition detected.")
        End If
        Log("Switching to Screen 1")
        Me.Screens.Pop()
        Me.CurrentScreen = Me.Screens.Peek()
    End Sub

    Public Sub GoForward()
        Log("Going forward.")
        If (Me.Screens.Count > 1) Then
            Throw New InvalidOperationException("Race condition detected.")
        End If
        Log("Switching to Screen 2.")

        Me.Screens.Push(New Screen2(Me))
        Me.CurrentScreen = Me.Screens.Peek()
    End Sub

End Class

Class Screen , containing only the delegate command to trigger the action:

Public Class Screen1
    Inherits PropertyChangedBase

    Private _clickCommand As ICommand
    Private _shellViewModel As ShellViewModel

    Public Sub New(parent As ShellViewModel)
        _shellViewModel = parent
    End Sub


    Public ReadOnly Property ClickCommand As ICommand
        Get
            If _clickCommand Is Nothing Then
                _clickCommand = New RelayCommand(AddressOf ExecuteClick, AddressOf CanExecute)
            End If
            Return _clickCommand
        End Get
    End Property

    Private Function CanExecute(arg As Object) As Boolean
        Return True
    End Function

    Private Sub ExecuteClick(obj As Object)
        Threading.Thread.SpinWait(100000000)
        _shellViewModel.GoForward()
    End Sub

End Class
+4
source share
4 answers

No weird race condition

I ran your code. There is one thread. Main.

One thread = no race condition.

?

, , () WPF .

. = ( , ).

, GoBack GoForward . .

, . - , .

, , , :

- , , , / .a >

!

Dispatcher.BeginInvoke Invoke, . , , .

, . , , , , . , , , . :

  • , .
  • , SpinWait .
  • - .

?

GoBack GoBackward , .

:

1. .

2. A bool ( ).

3. enum, .

4. .. ! .

. - ( )? ... , : Stack pop/push . ConcurrentStack<T>

+5

- , . ( #, ):

private void ButtonClick(object sender, EventArgs args)
    {
        Debug.WriteLine("start");
        Thread.Sleep(6000);
        Debug.WriteLine("End");
    }

, "", , . , 6 .

, , , - , . PropertyChanged ( , OnClick) . , . Thread.Sleep , , - .

, current, Changed. , . , .

, PropertyChanged , . , .

, "safepoint" - , , - , , Loaded Event.

Fab  :)

, , , . , - , .

  • IsEnabled, , IsHitTestVisible
  • -
  • , ( )
+2

- " ", " " . , , , , ​​ , ( , , , " " ), .

, - CanExecute, true (, , , ), _shellViewModel , , ExecuteClick ( GoForward true, ). ( ), , , CanExecute ExecuteClick, , "" ( false, GoForward ).

+1

@ :

, , . , WPF - click. , click . .

, : , , , , . , WPF - . .

, - . PropertyChanged, . , . , , .

@ Fab

, . , .

, , , . - , . , . , , , ( - ).

, . , , , ( ) . , ViewModel - , WPF .

, " ". Bulletproof WPF. (/) . , .

PS: , ConcurrentStack; -)

+1
source

Source: https://habr.com/ru/post/1625014/


All Articles