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
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
I ran your code. There is one thread. Main.
One thread = no race condition.
?
, , () WPF .
. = ( , ).
, GoBack GoForward . .
, . - , .
, , , :
- , , , / .a >
!
Dispatcher.BeginInvoke Invoke, . , , .
, . , , , , . , , , . :
GoBack GoBackward , .
:
1. .
2. A bool ( ).
bool
3. enum, .
enum
4. .. ! .
. - ( )? ... , : Stack pop/push . ConcurrentStack<T>
ConcurrentStack<T>
- , . ( #, ):
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 :)
, , , . , - , .
- " ", " " . , , , , , ( , , , " " ), .
, - CanExecute, true (, , , ), _shellViewModel , , ExecuteClick ( GoForward true, ). ( ), , , CanExecute ExecuteClick, , "" ( false, GoForward ).
CanExecute
true
_shellViewModel
ExecuteClick
GoForward
false
@ :
, , . , WPF - click. , click . .
, : , , , , . , WPF - . .
, - . PropertyChanged, . , . , , .
@ Fab
, . , .
, , , . - , . , . , , , ( - ).
, . , , , ( ) . , ViewModel - , WPF .
, " ". Bulletproof WPF. (/) . , .
PS: , ConcurrentStack; -)
Source: https://habr.com/ru/post/1625014/More articles:Building a Spirit parser programmatically, starting with EBNF grammar - c ++Могу ли я иметь динамические пользовательские разрешения с помощью AWS IAM/Cognito? - amazon-s3Mean.IO Stack - Ошибка Подключение к базе данных: {[MongoError: connect ECONNREFUSED 127.0.0.1:27017] - mongodbWebKit enforces garbage collection / behavior and debugs / detects leaks - javascriptMongoDB / Mongoose $ pull (delete) Sub Document not working - node.jsHow does someone debug Sails.js code in Visual Studio? - node.jsCan an Apple Watch vibrate a local notification only and not play a sound? - iosDoes Google Tag Manager container automatically update? If so, how / where? - androidRails redirect_to creates invalid URL - redirectМожно ли отредактировать буфер терминала в обычном режиме в NeoVim? - neovimAll Articles