Comment utiliser CancellationTokenSource pour fermer une boîte de dialog sur un autre thread?

Ceci est lié à mon autre question Comment annuler une impression en arrière-plan .

J’essaie de mieux comprendre le modèle CancellationTokenSource et comment l’utiliser au-delà des limites de thread.

J’ai une fenêtre principale (sur le thread d’interface utilisateur) où le code derrière fait:

public MainWindow() { InitializeComponent(); Loaded += (s, e) => { DataContext = new MainWindowViewModel(); Closing += ((MainWindowViewModel)DataContext).MainWindow_Closing; }; } 

qui appelle correctement le code CloseWindow lorsqu’il est fermé:

  private void CloseWindow(IClosable window) { if (window != null) { windowClosingCTS.Cancel(); window.Close(); } } 

Avec la sélection d’un élément de menu, une deuxième fenêtre est créée sur un fil de fond:

  // Print Preview public static void PrintPreview(FixedDocument fixeddocument, CancellationToken ct) { // Was cancellation already requested? if (ct.IsCancellationRequested) ct.ThrowIfCancellationRequested(); ............................... // Use my custom document viewer (the print button is removed). var previewWindow = new PrintPreview(fixedDocumentSequence); //Register the cancellation procedure with the cancellation token ct.Register(() => previewWindow.Close() ); previewWindow.ShowDialog(); } } 

Dans MainWindowViewModel (sur le fil de l’interface utilisateur), j’ai mis:

 public CancellationTokenSource windowClosingCTS { get; set; } 

Avec son constructeur de:

  // Constructor public MainMenu() { readers = new List(); CloseWindowCommand = new RelayCommand(this.CloseWindow); windowClosingCTS = new CancellationTokenSource(); } 

Maintenant mon problème. WindowClosingCTS.Cancel () provoque la fermeture d’un appel immédiat au délégué inscrit auprès de ct, c.-à-d. Que previewWindow.Close () est appelé. Cela renvoie maintenant immédiatement à la “Si (Windows! = Null) avec:

“Le thread appelant ne peut pas accéder à cet object car un autre thread le possède.”

Alors qu’est-ce que je fais mal?

Votre problème est que votre fenêtre d’aperçu s’exécute sur un autre thread. Lorsque vous déclenchez l’annulation, vous exécutez l’action enregistrée du jeton d’annulation sur ce thread, et non sur le thread sur lequel l’aperçu est en cours d’exécution.

La règle d’or dans ces cas est de ne pas utiliser deux threads d’interface utilisateur. Cela causera généralement des problèmes et le travail que vous devez faire pour les gérer n’en vaut généralement pas la peine.

Si vous souhaitez conserver votre solution ou si vous souhaitez déclencher l’annulation d’un thread d’arrière-plan, vous devez organiser votre opération de fermeture du thread dans lequel votre fenêtre est ouverte:

 Action closeAction = () => previewWindow.Close(); previewWindow.Dispatcher.Invoke(closeAction); 

Le problème avec votre code est

Avec la sélection d’un élément de menu, une deuxième fenêtre est créée sur un fil de fond:

 // Print Preview public static void PrintPreview(FixedDocument fixeddocument, CancellationToken ct) { // Was cancellation already requested? if (ct.IsCancellationRequested) ct.ThrowIfCancellationRequested(); ............................... // Use my custom document viewer (the print button is removed). var previewWindow = new PrintPreview(fixedDocumentSequence); //Register the cancellation procedure with the cancellation token ct.Register(() => previewWindow.Close() ); previewWindow.ShowDialog(); } } 

Et ce que je présume être

 Task.Run(() => PrintPreview(foo, cancel)); 

La bonne solution consiste à tout faire sur un seul thread.

 public static Task PrintPreview(FixedDocument fixeddocument, CancellationToken ct) { var tcs = new TaskCompletionSource(); // Was cancellation already requested? if (ct.IsCancellationRequested) tcs.SetResult(false); else { // Use my custom document viewer (the print button is removed). var previewWindow = new PrintPreview(fixedDocumentSequence); //Register the cancellation procedure with the cancellation token ct.Register(() => previewWindow.Close()); previewWindow.Closed += (o, e) => { var result = previewWindow.DialogResult; if (result.HasValue) tcs.SetResult(result.Value); else tcs.SetResult(false); } previewWindow.Show(); } return tcs.Task; } 

Alors appelez

  var shouldPrint = await PrintPreview(foo, cancel); if (shouldPrint) await PrintAsync(foo);