.NET: How to ensure that Thread 1 can see what Thread 2 has written in a field?
Environment: .NET 3.5 SP1.
I've got two threads: UI thread and a background worker thread. The background worker thread periodically updates some fields in a shared object and the UI thread checks them. Nothing spectacular - just the progress, return values and thrown exceptions. Also the worker thread raises some events on the UI thread (via Control.BeginInvoke
) when it changes these fields.
The worker thread ONLY WRITES these fields, and the UI thread ONLY READS them. They are not used for any other communication. For the sake of performance I'd like to avoid locking on the shared object or the individual properties. There will never be an invalid state in the shared object.
However I'm worried about things like processor caches and compiler optimizations. How can I avoid the situation when an updated value is not visible in the 开发者_开发问答event handler on the UI thread? Will adding volatile
to all fields be enough?
You're okay, no need to worry. A memory barrier is required to flush any pending writes to memory. There's an implicit one with any lock statement. Control.Begin/Invoke() needs to take a lock to protect the list of pending delegates so that's sufficient.
The volatile requirement is a harder one, mostly because its exact semantics are so poorly documented. On x86/x64 hardware it merely prevents the JIT compiler from caching the value of a variable in a CPU register. This is not an issue in your case because the delegate target points to a method. Variables are not cached across methods if the methods are not inlined. Your delegate target cannot be inlined.
It is far, far better to use established multithreading guidelines. Producer/consumer collections are available in .NET 4.0; these are also available for .NET 3.5 if you include references to the Rx library.
Lock-free code is almost impossible to get right. If you don't want to use the producer/consumer queue, then use a lock.
If you insist on going down the path of pain, then yes, volatile
will enable any thread reading that variable to get the last written value.
Generall speaking I advise against using advanced synchronization mechanisms because they are notoriously difficult to get right, but in this case a call to Thread.MemoryBarrier
could be acceptable. Of course that assumes there are no atomicity requirements and the shared object can never be in a half-baked state. This might actually be easier than sprinkling everything with volatile
.
object shared;
void WorkerThread()
{
MakeChangesToSharedObject(shared);
Thread.MemoryBarrier(); // commit the changes to main memory
}
void UIThread()
{
Thread.MemoryBarrier(); // ensure updates are read from main memory
UseSharedObject(shared);
}
Perhaps designing the code so that the shared object is immutable would be better though. Then all you have to do is swap out the shared object reference in one simple and quick atomic operation.
volatile object shared;
void WorkerThread()
{
// The following operation is safe because the variable is volatile.
shared = GetNewSharedObject();
}
void UIThread()
{
// The following operation is safe because the variable is volatile.
object value = shared.SomeProperty;
}
精彩评论