2 // Copyright (c) Microsoft Corporation. All rights reserved.
5 namespace System.Activities.Presentation.Validation
7 using System.Activities.Validation;
9 using System.Threading;
10 using System.Windows.Threading;
12 internal class BackgroundValidationSynchronizer<TValidationResult> : ValidationSynchronizer
14 internal readonly SynchronizerState Idle;
15 internal readonly SynchronizerState Validating;
16 internal readonly SynchronizerState CancellingForNextValidation;
17 internal readonly SynchronizerState CancellingForDeactivation;
18 internal readonly SynchronizerState ValidationDeactivated;
20 private readonly object thisLock = new object();
22 private Func<ValidationReason, CancellationToken, TValidationResult> validationWork;
23 private Action<TValidationResult> updateWork;
25 private CancellationTokenSource cancellationTokenSource;
26 private TaskDispatcher dispatcher;
28 private SynchronizerState currentState;
29 private ValidationReason lastValidationReason;
31 private int deactivationReferenceCount;
33 internal BackgroundValidationSynchronizer(TaskDispatcher dispatcher, Func<ValidationReason, CancellationToken, TValidationResult> validationWork, Action<TValidationResult> updateWork)
35 Fx.Assert(validationWork != null, "validationWork should not be null and is ensured by caller.");
36 Fx.Assert(updateWork != null, "updateWork should not be null and is ensured by caller.");
38 this.Idle = new IdleState(this);
39 this.Validating = new ValidatingState(this);
40 this.CancellingForNextValidation = new CancellingForNextValidationState(this);
41 this.CancellingForDeactivation = new CancellingForDeactivationState(this);
42 this.ValidationDeactivated = new ValidationDeactivatedState(this);
43 this.dispatcher = dispatcher;
44 this.validationWork = validationWork;
45 this.updateWork = updateWork;
46 this.currentState = this.Idle;
49 internal SynchronizerState CurrentState
53 return this.currentState;
58 this.currentState = value;
59 this.OnCurrentStateChanged();
63 internal override void Validate(ValidationReason validationReason)
67 this.CurrentState = this.CurrentState.Validate(validationReason);
71 internal override void DeactivateValidation()
75 Fx.Assert(this.deactivationReferenceCount >= 0, "It should never happen -- deactivationReferenceCount < 0.");
76 if (this.deactivationReferenceCount == 0)
78 this.CurrentState = this.CurrentState.DeactivateValidation();
79 if (this.CurrentState != this.ValidationDeactivated)
81 Monitor.Wait(this.thisLock);
85 this.deactivationReferenceCount++;
89 internal override void ActivateValidation()
93 this.deactivationReferenceCount--;
94 Fx.Assert(this.deactivationReferenceCount >= 0, "It should never happen -- deactivationReferenceCount < 0.");
95 if (this.deactivationReferenceCount == 0)
97 this.CurrentState = this.CurrentState.ActivateValidation();
102 // for unit test only
103 protected virtual void OnCurrentStateChanged()
107 private void ValidationCompleted(TValidationResult result)
111 this.CurrentState = this.CurrentState.ValidationCompleted(result);
115 private void ValidationCancelled()
119 this.CurrentState = this.CurrentState.ValidationCancelled();
123 private void CancellableValidate(object state)
125 Fx.Assert(state is ValidationReason, "unusedState should always be a ValidationReason.");
126 ValidationReason reason = (ValidationReason)state;
129 Fx.Assert(this.cancellationTokenSource != null, "this.cancellationTokenSource should be constructed");
130 TValidationResult result = this.validationWork(reason, this.cancellationTokenSource.Token);
131 this.ValidationCompleted(result);
133 catch (OperationCanceledException)
135 this.ValidationCancelled();
139 private void Cancel()
141 Fx.Assert(this.cancellationTokenSource != null, "Cancel should be called only when the work is active, and by the time the cancellationTokenSource should not be null.");
142 Fx.Assert(this.cancellationTokenSource.IsCancellationRequested == false, "We should only request for cancel once.");
143 this.cancellationTokenSource.Cancel();
146 private void ValidationWork(ValidationReason reason)
148 this.cancellationTokenSource = new CancellationTokenSource();
149 this.dispatcher.DispatchWorkOnBackgroundThread(Fx.ThunkCallback(new WaitCallback(this.CancellableValidate)), reason);
152 private void UpdateUI(TValidationResult result)
154 this.dispatcher.DispatchWorkOnUIThread(DispatcherPriority.ApplicationIdle, new Action(() => { this.updateWork(result); }));
157 internal abstract class SynchronizerState
159 public SynchronizerState(BackgroundValidationSynchronizer<TValidationResult> parent)
161 Fx.Assert(parent != null, "parent should not be null.");
163 this.Parent = parent;
166 protected BackgroundValidationSynchronizer<TValidationResult> Parent { get; private set; }
168 public abstract SynchronizerState Validate(ValidationReason reason);
170 public abstract SynchronizerState ValidationCompleted(TValidationResult result);
172 public abstract SynchronizerState ValidationCancelled();
174 public abstract SynchronizerState DeactivateValidation();
176 public abstract SynchronizerState ActivateValidation();
179 private class IdleState : SynchronizerState
181 public IdleState(BackgroundValidationSynchronizer<TValidationResult> parent)
186 public override SynchronizerState Validate(ValidationReason reason)
188 this.Parent.ValidationWork(reason);
189 return this.Parent.Validating;
192 public override SynchronizerState ValidationCompleted(TValidationResult result)
194 Fx.Assert(false, "This should never happen - we are idle, so there is no work to complete.");
198 public override SynchronizerState ValidationCancelled()
200 Fx.Assert(false, "This should never happen - we are idle, so there is no work to be cancelled.");
204 public override SynchronizerState DeactivateValidation()
206 return this.Parent.ValidationDeactivated;
209 public override SynchronizerState ActivateValidation()
211 Fx.Assert(false, "This should never happen - validation hasn't been deactivated, so there is no possibility for activate validation.");
216 private class ValidatingState : SynchronizerState
218 public ValidatingState(BackgroundValidationSynchronizer<TValidationResult> parent)
223 public override SynchronizerState Validate(ValidationReason reason)
225 this.Parent.Cancel();
226 this.Parent.lastValidationReason = reason;
227 return this.Parent.CancellingForNextValidation;
230 public override SynchronizerState ValidationCompleted(TValidationResult result)
232 this.Parent.cancellationTokenSource = null;
233 this.Parent.UpdateUI(result);
234 return this.Parent.Idle;
237 public override SynchronizerState ValidationCancelled()
239 Fx.Assert(false, "This should never happen - we haven't request for cancel yet, so there is no work to be cancelled.");
243 public override SynchronizerState DeactivateValidation()
245 this.Parent.Cancel();
246 return this.Parent.CancellingForDeactivation;
249 public override SynchronizerState ActivateValidation()
251 Fx.Assert(false, "This should never happen - validation hasn't been deactivated, so there is no possibility for activate validation.");
256 private class CancellingForNextValidationState : SynchronizerState
258 public CancellingForNextValidationState(BackgroundValidationSynchronizer<TValidationResult> parent)
263 public override SynchronizerState Validate(ValidationReason reason)
265 this.Parent.lastValidationReason = reason;
266 return this.Parent.CancellingForNextValidation;
269 public override SynchronizerState ValidationCompleted(TValidationResult result)
271 this.Parent.cancellationTokenSource = null;
272 this.Parent.UpdateUI(result);
273 this.Parent.ValidationWork(this.Parent.lastValidationReason);
274 return this.Parent.Validating;
277 public override SynchronizerState ValidationCancelled()
279 this.Parent.cancellationTokenSource = null;
280 this.Parent.ValidationWork(this.Parent.lastValidationReason);
281 return this.Parent.Validating;
284 public override SynchronizerState DeactivateValidation()
286 return this.Parent.CancellingForDeactivation;
289 public override SynchronizerState ActivateValidation()
291 Fx.Assert(false, "This should never happen - validation hasn't been deactivated, so there is no possibility for activate validation.");
296 private class CancellingForDeactivationState : SynchronizerState
298 public CancellingForDeactivationState(BackgroundValidationSynchronizer<TValidationResult> parent)
303 public override SynchronizerState Validate(ValidationReason reason)
305 // Validation need to give way to commit so that we have a responsive UI.
306 return this.Parent.CancellingForDeactivation;
309 public override SynchronizerState ValidationCompleted(TValidationResult result)
311 this.Parent.cancellationTokenSource = null;
312 this.Parent.UpdateUI(result);
313 Monitor.Pulse(this.Parent.thisLock);
314 return this.Parent.ValidationDeactivated;
317 public override SynchronizerState ValidationCancelled()
319 this.Parent.cancellationTokenSource = null;
320 Monitor.Pulse(this.Parent.thisLock);
321 return this.Parent.ValidationDeactivated;
324 public override SynchronizerState DeactivateValidation()
326 return this.Parent.CancellingForDeactivation;
329 public override SynchronizerState ActivateValidation()
331 Fx.Assert(false, "This should never happen - validation hasn't been deactivated, so there is no possibility for activate validation.");
336 private class ValidationDeactivatedState : SynchronizerState
338 public ValidationDeactivatedState(BackgroundValidationSynchronizer<TValidationResult> parent)
343 public override SynchronizerState Validate(ValidationReason reason)
345 // no-op - because commit will trigger validation anyway.
346 return this.Parent.ValidationDeactivated;
349 public override SynchronizerState ValidationCompleted(TValidationResult result)
351 Fx.Assert(false, "This should never happen - we are committing, so there is no validation work in progress, not to mention the possibility for them to be completed.");
355 public override SynchronizerState ValidationCancelled()
357 Fx.Assert(false, "This should never happen - we are committing, not to mention the possibility for them to be cancelled.");
361 public override SynchronizerState DeactivateValidation()
363 Fx.Assert(false, "This should never happen - validation has already been deactivated, so we shouldn't DeactivateValidation again.");
367 public override SynchronizerState ActivateValidation()
369 return this.Parent.Idle;