问题
The way to cancel a BackgroundWorker's operation is to call BackgroundWorker.CancelAsync():
// RUNNING IN UI THREAD
private void cancelButton_Click(object sender, EventArgs e)
{
backgroundWorker.CancelAsync();
}
In a BackgroundWorker.DoWork event handler, we check BackgroundWorker.CancellationPending:
// RUNNING IN WORKER THREAD
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!backgroundWorker.CancellationPending) {
DoSomething();
}
}
The above idea is all over the web, including on the MSDN page for BackgroundWorker.
Now, my question is this: How on earth is this thread-safe?
I've looked at the BackgroundWorker class in ILSpy — CancelAsync() simply sets cancellationPending to true without using a memory barrier, and CancellationPending simply returns cancellationPending without using a memory barrier.
According to this Jon Skeet page, the above is not thread-safe. But the documentation for BackgroundWorker.CancellationPending says, "This property is meant for use by the worker thread, which should periodically check CancellationPending and abort the background operation when it is set to true."
What's going on here? Is it thread-safe or not?
回答1:
It is thread-safe.
The code
while (!backgroundWorker.CancellationPending)
is reading a property and the compiler knows it can't cache the result.
And since in the normal framework every Write is the same as VolatileWrite, the CancelAsync() method can just set a field.
回答2:
It is because BackgroundWorker inherits from Component which inherits from MarshalByRefObject. An MBRO object may reside on another machine or in another process or appdomain. That works by having the object impersonated by a proxy that has all of the exact same properties and methods but whose implementations marshal the call across the wire.
One side effect of that is that the jitter cannot inline methods and properties, that would break the proxy. Which also prevents any optimizations from being made that stores the field value in a cpu register. Just like volatile does.
回答3:
A good point and a very fair doubt. It sounds like not thread safe. I think MSDN also has the same doubt and that's why this is there under BackgroundWorker.CancelAsync Method.
Caution
Be aware that your code in the DoWork event handler may finish its work as a cancellation request is being made, and your polling loop may miss CancellationPending being set to true. In this case, the Cancelled flag of System.ComponentModel.RunWorkerCompletedEventArgs in your RunWorkerCompleted event handler will not be set to true, even though a cancellation request was made. This situation is called a race condition and is a common concern in multithreaded programming. For more information about multithreading design issues, see Managed Threading Best Practices.
I am not sure about BackgroundWorker class implementation. Looking at how it is using BackgroundWorker.WorkerSupportsCancellation property internally is important in this case.
回答4:
Question can be interpreted in two ways:
1) Is BackgroundWorker.CancellationPending implementation correct?
It is not correct because it may result in cancellation request being unnoticed. The implementation uses ordinary read from the backing field. If this backing field is updated by other threads then the update may be invisible to the reading code. This is what implementation looks like:
// non volatile variable:
private Boolean cancellationPending;
public Boolean CancellationPending {
get {
// ordinary read:
return cancellationPending;
}
}
The correct implementation would try to read the most up to date value. This can be achieved by declaring backing field 'volatile', using memory barrier or lock. There are probably other options and some of them are better than the others but is up to the team that owns 'BackgroundWorker' class.
2) Is code that uses BackgroundWorker.CancellationPending correct?
while (!backgroundWorker.CancellationPending) {
DoSomething();
}
This code is correct. The loop will spin until CancellationPending returns 'true'. Keeping in mind that C# properties is just a syntax sugar for CLR methods. At the IL level this is just another method that will look like "get_CancellationPending". Method return values are not cached by calling code (probably because figuring whether method has side effects is too hard).
来源:https://stackoverflow.com/questions/6966573/how-is-backgroundworker-cancellationpending-thread-safe