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);
}
}
Alors, quel est le problème? – Kiril
On dirait la même chose ... avez-vous des problèmes? – Kiril
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. –