Considérons une application Winforms, où nous avons un bouton qui génère des résultats. Si l’utilisateur appuie une seconde fois sur le bouton, il doit annuler la première demande pour générer des résultats et en démarrer une nouvelle.
Nous utilisons le modèle ci-dessous, mais nous ne soaps pas si une partie du code est nécessaire pour éviter une condition de concurrence (voir les lignes commentées).
private CancellationTokenSource m_cts; private void generateResultsButton_Click(object sender, EventArgs e) { // Cancel the current generation of results if necessary if (m_cts != null) m_cts.Cancel(); m_cts = new CancellationTokenSource(); CancellationToken ct = m_cts.Token; // **Edit** Clearing out the label m_label.Text = Ssortingng.Empty; // **Edit** Task task = Task.Run(() => { // Code here to generate results. return 0; }, ct); task.ContinueWith(t => { // Is this code necessary to prevent a race condition? // if (ct.IsCancellationRequested) // return; int result = t.Result; m_label.Text = result.ToSsortingng(); }, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext()); }
Remarquer:
CancellationTokenSource
sur le fil principal. CancellationToken
dans la suite que dans la tâche initiale. Nous nous demandons si la séquence d’événements suivante est possible ou non:
CancellationToken
n’est pas encore annulé). Le planificateur de tâches poste le travail dans la file de messages Windows (pour l’exécuter sur le thread principal). CancellationTokenSource
est annulé. Donc, je pense que la question se résume à:
Lorsque le travail est posté sur le thread principal (à l’aide de TaskScheduler.FromCurrentSynchronizationContext()
), le TPL vérifie-t-il CancellationToken
sur le thread principal avant d’exécuter l’action de la tâche, ou puis poster le travail sur le SynchronizationContext
?
En supposant que je lise la question correctement, vous êtes inquiet à propos de la séquence d’événements suivante:
T0
est planifiée sur le pool de threads, la suite C0
est planifiée comme une continuation de T0
et doit être exécutée sur le planificateur de tâches du contexte de synchronisation. T0
termine, C0
est envoyé dans la file de messages. La queue contient maintenant deux éléments, le gestionnaire de clics et l’exécution de C0
. T0
et de C0
. Ensuite, il planifie T1
sur le pool de threads et C1
en continu de la même manière qu’à l’étape 1
. C0
‘ est toujours dans la queue, il est donc traité maintenant. Est-ce qu’il exécute la continuation que vous aviez l’intention d’annuler? La réponse est non. TryExecuteTask n’exécutera pas une tâche dont l’annulation a été signalée. C’est impliqué par cette documentation, mais explicitement énoncé sur la page TaskStatus , qui spécifie
Annulé – La tâche a accusé réception de l’annulation en lançant une OperationCanceledException avec son propre AnnulationToken alors que le jeton était dans l’état signalé ou que AnnulationToken de la tâche était déjà signalé avant que la tâche ne commence à s’exécuter .
Donc, à la fin de la journée, T0
sera dans l’état RanToCompletion
et C0
sera dans l’état Canceled
.
C’est tout, bien sûr, en supposant que le SynchronizationContext
ne permet pas l’exécution simultanée de tâches (comme vous le savez, le Windows Forms ne le fait pas – je remarque simplement que ce n’est pas une exigence des contextes de synchronisation).
En outre, il convient de noter que la réponse exacte à votre dernière question, à savoir si le jeton d’annulation est vérifié dans le contexte de la demande d’annulation ou du moment où la tâche est exécutée, correspond réellement aux deux . En plus de la vérification finale dans TryExecuteTask
, dès que l’annulation est demandée, la structure appellera TryDequeue
, opération facultative que les planificateurs de tâches peuvent prendre en charge. Le planificateur de contexte de synchronisation ne le prend pas en charge. Mais si cela se produisait, la différence pourrait être que le message ‘exécuter C0
‘ serait entièrement extrait de la file de messages du thread et qu’il ne tenterait même pas d’exécuter la tâche.
À mon avis, quel que soit le thread qui vérifie CencellationToken, vous devez prendre en compte la possibilité que votre continuation puisse être planifiée et que l’utilisateur puisse annuler la demande pendant l’exécution de la continuation. Donc, je pense que le contrôle qui a été commenté devrait être vérifié et devrait probablement être vérifié à nouveau après avoir lu le résultat:
task.ContinueWith(t => { // Is this code necessary to prevent a race condition? if (ct.IsCancellationRequested) return; int result = t.Result; if (ct.IsCancellationRequested) return; m_label.Text = result.ToSsortingng(); }, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
J’appendais également une continuité pour traiter la condition d’annulation séparément:
task.ContinueWith(t => { // Do whatever is appropriate here. }, ct, TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
De cette façon, vous avez toutes les possibilités couvertes.