How is BackgroundWorker.CancellationPending thread-safe?

末鹿安然 提交于 2019-11-28 19:20:25

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.

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.

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.

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).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!