我坚信任何依赖于InvokeRequired 的代码都遵循着糟糕的模式。
每个方法都应该知道它正在UI线程的上下文中运行,或者知道它不是。可以从 any 线程运行的唯一方法应该是同步类,例如 Semaphore(如果您正在编写同步类,请问问自己是否真的应该这样做)。
这是多线程设计的一个原则,可以减少错误并澄清代码。
对于从后台线程更新 UI 的问题,我建议使用调度到 UI 的SynchronizationContext 的Task 对象。如果 Task 类不可用(例如,针对 4.0 之前的框架),则使用 BackgroundWorker。
这是一个支持向 UI 报告进度以及支持取消和错误情况的后台任务示例:
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();
}
}
此示例代码使用我为方便而定义的ProgressReporter(它清理了代码,IMO)。此类型为defined on my blog:
/// <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);
}
}