2010-07-15 6 views
2

Pour commencer, je ne suis pas expérimenté dans le développement de Window.Forms. Cependant, je trouve les contrôles InvokeRequired, pour les contrôles, un peu fastidieux lorsqu'il est utilisé dans une application threadée. J'ai créé une méthode statique qui, je pense, résout mes fastidieuses vérifications InvokeRequired. Je veux juste jeter ceci à l'air libre pour voir si son mauvais « modèle »:Méthode InvokeRequired - aide codereview

public static void UIInvoke(Control uiControl, Action action) 
{ 
    if (!uiControl.IsDisposed) 
    { 
     if (uiControl.InvokeRequired) 
     { 
      uiControl.BeginInvoke(action); 
     } 
     else 
     { 
      action(); 
     } 
    } 
} 

OK, donc j'ai une zone de texte (nom StatusTextBox) que je veux mettre un texte dans un fil d'arrière-plan. Le code pour cela serait:

ThreadUtilities.UIInvoke(this.StatusTextBox, delegate() 
{ 
    string text = this.StatusTextBox.Text; 
    this.StatusTextBox.Text = (text.Length > 10) ? String.Empty : text.PadRight(1, '.'); 
}); 

Est-ce la même chose que?

this.StatusTextBox.BeginInvoke(delegate() 
{ 
    string text = this.StatusTextBox.Text; 
    this.StatusTextBox.Text = (text.Length > 10) ? String.Empty : text.PadRight(1, '.'); 
}); 

Google à son meilleur, trouvé this article où quelqu'un est venu avec la même approche. Je vais continuer mon "harcèlement" à la place. Merci!

+1

Alors, quel est le problème? – Kiril

+0

On dirait la même chose ... avez-vous des problèmes? – Kiril

+0

Le problème est que je ne suis pas sûr à 100% que c'est une bonne approche, donc je veux des suggestions ou des commentaires. –

Répondre

3

Je crois fermement que tout code dépendant de InvokeRequired suit un mauvais schéma.

Chaque méthode doit soit savoir qu'il est en cours d'exécution dans le contexte du thread d'interface utilisateur, ou savoir qu'il n'est pas. Les seules méthodes pouvant être exécutées à partir de un thread doivent être sur des classes de synchronisation telles que Semaphore (et si vous écrivez un cours de synchronisation, demandez-vous si vous devriez vraiment le faire).

Ceci est un principe de conception multithread qui réduit les bugs et clarifie le code.

Pour le problème de la mise à jour de l'interface utilisateur à partir d'un thread d'arrière-plan, je recommande d'utiliser un objet Task planifié pour l'interface utilisateur SynchronizationContext. Si la classe Task n'est pas disponible (par exemple, en ciblant une infrastructure antérieure à la version 4.0), utilisez BackgroundWorker.

Voici un exemple d'une tâche d'arrière-plan qui prend en charge les rapports d'étape à l'interface utilisateur, ainsi que l'appui annulation et situations d'erreur:

using System; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

class Program 
{ 
    [STAThread] 
    static void Main() 
    { 
    // Set up the UI and run it. 

    var program = new Program 
    { 
     startButton = new Button 
     { 
     Text = "Start", 
     Height = 23, Width = 75, 
     Left = 12, Top = 12, 
     }, 
     cancelButton = new Button 
     { 
     Text = "Cancel", 
     Enabled = false, 
     Height = 23, Width = 75, 
     Left = 93, Top = 12, 
     }, 
     progressBar = new ProgressBar 
     { 
     Width = 156, Height = 23, 
     Left = 12, Top = 41, 
     }, 
    }; 

    var form = new Form 
    { 
     Controls = 
     { 
      program.startButton, 
      program.cancelButton, 
      program.progressBar 
     }, 
    }; 

    program.startButton.Click += program.startButton_Click; 
    program.cancelButton.Click += program.cancelButton_Click; 
    Application.Run(form); 
    } 

    public Button startButton; 
    public Button cancelButton; 
    public ProgressBar progressBar; 

    private CancellationTokenSource cancellationTokenSource; 

    private void startButton_Click(object sender, EventArgs e) 
    { 
    this.startButton.Enabled = false; 
    this.cancelButton.Enabled = true; 

    this.cancellationTokenSource = new CancellationTokenSource(); 
    var cancellationToken = this.cancellationTokenSource.Token; 
    var progressReporter = new ProgressReporter(); 
    var task = Task.Factory.StartNew(() => 
    { 
     for (int i = 0; i != 100; ++i) 
     { 
     // Check for cancellation 
     cancellationToken.ThrowIfCancellationRequested(); 

     Thread.Sleep(30); // Do some work. 

     // Report progress of the work. 
     progressReporter.ReportProgress(() => 
     { 
      // Note: code passed to "ReportProgress" may access UI elements. 
      this.progressBar.Value = i; 
     }); 
     } 

     // Uncomment the next line to play with error handling. 
     //throw new InvalidOperationException("Oops..."); 

     // The answer, at last! 
     return 42; 
    }, cancellationToken); 

    // ProgressReporter can be used to report successful completion, 
    // cancelation, or failure to the UI thread. 
    progressReporter.RegisterContinuation(task,() => 
    { 
     // Update UI to reflect completion. 
     this.progressBar.Value = 100; 

     // Display results. 
     if (task.Exception != null) 
     MessageBox.Show("Background task error: " + task.Exception.ToString()); 
     else if (task.IsCanceled) 
     MessageBox.Show("Background task cancelled"); 
     else 
     MessageBox.Show("Background task result: " + task.Result); 

     // Reset UI. 
     this.progressBar.Value = 0; 
     this.startButton.Enabled = true; 
     this.cancelButton.Enabled = false; 
    }); 
    } 

    private void cancelButton_Click(object sender, EventArgs e) 
    { 
    this.cancellationTokenSource.Cancel(); 
    } 
} 

Ce code exemple utilise un ProgressReporter que je définissais pour la commodité (il nettoie la code, OMI). Ce type est defined on my blog as:

/// <summary> 
/// A class used by Tasks to report progress or completion updates back to the UI. 
/// </summary> 
public sealed class ProgressReporter 
{ 
    /// <summary> 
    /// The underlying scheduler for the UI's synchronization context. 
    /// </summary> 
    private readonly TaskScheduler scheduler; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ProgressReporter"/> class. This should be run on a UI thread. 
    /// </summary> 
    public ProgressReporter() 
    { 
    this.scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 
    } 

    /// <summary> 
    /// Gets the task scheduler which executes tasks on the UI thread. 
    /// </summary> 
    public TaskScheduler Scheduler 
    { 
    get { return this.scheduler; } 
    } 

    /// <summary> 
    /// Reports the progress to the UI thread. This method should be called from the task. Note that the progress update is asynchronous with respect to the reporting Task. For a synchronous progress update, wait on the returned <see cref="Task"/>. 
    /// </summary> 
    /// <param name="action">The action to perform in the context of the UI thread. Note that this action is run asynchronously on the UI thread.</param> 
    /// <returns>The task queued to the UI thread.</returns> 
    public Task ReportProgressAsync(Action action) 
    { 
    return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, this.scheduler); 
    } 

    /// <summary> 
    /// Reports the progress to the UI thread, and waits for the UI thread to process the update before returning. This method should be called from the task. 
    /// </summary> 
    /// <param name="action">The action to perform in the context of the UI thread.</param> 
    public void ReportProgress(Action action) 
    { 
    this.ReportProgressAsync(action).Wait(); 
    } 

    /// <summary> 
    /// Registers a UI thread handler for when the specified task finishes execution, whether it finishes with success, failiure, or cancellation. 
    /// </summary> 
    /// <param name="task">The task to monitor for completion.</param> 
    /// <param name="action">The action to take when the task has completed, in the context of the UI thread.</param> 
    /// <returns>The continuation created to handle completion. This is normally ignored.</returns> 
    public Task RegisterContinuation(Task task, Action action) 
    { 
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.None, this.scheduler); 
    } 

    /// <summary> 
    /// Registers a UI thread handler for when the specified task finishes execution, whether it finishes with success, failiure, or cancellation. 
    /// </summary> 
    /// <typeparam name="TResult">The type of the task result.</typeparam> 
    /// <param name="task">The task to monitor for completion.</param> 
    /// <param name="action">The action to take when the task has completed, in the context of the UI thread.</param> 
    /// <returns>The continuation created to handle completion. This is normally ignored.</returns> 
    public Task RegisterContinuation<TResult>(Task<TResult> task, Action action) 
    { 
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.None, this.scheduler); 
    } 

    /// <summary> 
    /// Registers a UI thread handler for when the specified task successfully finishes execution. 
    /// </summary> 
    /// <param name="task">The task to monitor for successful completion.</param> 
    /// <param name="action">The action to take when the task has successfully completed, in the context of the UI thread.</param> 
    /// <returns>The continuation created to handle successful completion. This is normally ignored.</returns> 
    public Task RegisterSucceededHandler(Task task, Action action) 
    { 
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, this.scheduler); 
    } 

    /// <summary> 
    /// Registers a UI thread handler for when the specified task successfully finishes execution and returns a result. 
    /// </summary> 
    /// <typeparam name="TResult">The type of the task result.</typeparam> 
    /// <param name="task">The task to monitor for successful completion.</param> 
    /// <param name="action">The action to take when the task has successfully completed, in the context of the UI thread. The argument to the action is the return value of the task.</param> 
    /// <returns>The continuation created to handle successful completion. This is normally ignored.</returns> 
    public Task RegisterSucceededHandler<TResult>(Task<TResult> task, Action<TResult> action) 
    { 
    return task.ContinueWith(t => action(t.Result), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, this.Scheduler); 
    } 

    /// <summary> 
    /// Registers a UI thread handler for when the specified task becomes faulted. 
    /// </summary> 
    /// <param name="task">The task to monitor for faulting.</param> 
    /// <param name="action">The action to take when the task has faulted, in the context of the UI thread.</param> 
    /// <returns>The continuation created to handle faulting. This is normally ignored.</returns> 
    public Task RegisterFaultedHandler(Task task, Action<Exception> action) 
    { 
    return task.ContinueWith(t => action(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, this.Scheduler); 
    } 

    /// <summary> 
    /// Registers a UI thread handler for when the specified task becomes faulted. 
    /// </summary> 
    /// <typeparam name="TResult">The type of the task result.</typeparam> 
    /// <param name="task">The task to monitor for faulting.</param> 
    /// <param name="action">The action to take when the task has faulted, in the context of the UI thread.</param> 
    /// <returns>The continuation created to handle faulting. This is normally ignored.</returns> 
    public Task RegisterFaultedHandler<TResult>(Task<TResult> task, Action<Exception> action) 
    { 
    return task.ContinueWith(t => action(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, this.Scheduler); 
    } 

    /// <summary> 
    /// Registers a UI thread handler for when the specified task is cancelled. 
    /// </summary> 
    /// <param name="task">The task to monitor for cancellation.</param> 
    /// <param name="action">The action to take when the task is cancelled, in the context of the UI thread.</param> 
    /// <returns>The continuation created to handle cancellation. This is normally ignored.</returns> 
    public Task RegisterCancelledHandler(Task task, Action action) 
    { 
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, this.Scheduler); 
    } 

    /// <summary> 
    /// Registers a UI thread handler for when the specified task is cancelled. 
    /// </summary> 
    /// <typeparam name="TResult">The type of the task result.</typeparam> 
    /// <param name="task">The task to monitor for cancellation.</param> 
    /// <param name="action">The action to take when the task is cancelled, in the context of the UI thread.</param> 
    /// <returns>The continuation created to handle cancellation. This is normally ignored.</returns> 
    public Task RegisterCancelledHandler<TResult>(Task<TResult> task, Action action) 
    { 
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, this.Scheduler); 
    } 
} 
+0

C'était un peu plus que je peux mâcher en ce moment, mais merci pour l'info. Je vais vérifier dans les classes que vous avez utilisées. Je pense toujours que c'est beaucoup de code pour juste mettre à jour une progression, et je ne pense pas que ce soit plus lisible pour d'autres développeurs si l'endroit où prendre le contrôle du code.Pourquoi, à votre avis, est le mauvais modèle InvokeRequired, avez-vous des sources (et ne dites pas le google) je peux lire sur le sujet? –

+0

Mon préjugé contre 'InvokeRequired' est entièrement de [expérience personnelle] (http://stackoverflow.com/questions/3179640/multithread-debugging-techniques/3213742#3213742); il est plus facile de raisonner sur l'exactitude du code si cette propriété n'est jamais utilisée. Notez également qu'aucun équivalent de 'InvokeRequired' n'existe pour tout type de projet autre que Windows Forms. WPF, Windows Services, les programmes Console, ASP.NET et Silverlight utilisent tous l'abstraction 'SynchronizationContext' plus moderne (que ma solution utilise aussi). –

+0

En ce qui concerne la classe 'ProgressUpdater': oui, c'est une bonne quantité de code (la plus grande partie n'est pas utilisée dans cet exemple simple). Je pense que c'est plus propre que d'utiliser 'TaskScheduler' directement, et ce ne serait pas un problème de passer à un autre développeur s'il était utilisé dans le cadre d'une norme de codage. Je crois qu'une classe semblable à celle-ci deviendra bientôt une partie de l'usage commun. –

0

Votre méthode UIInvoke statique semble bonne et je ne vois rien de mal à cela.

Questions connexes