A common way to avoid data races is to enclose all public instance methods of a class with a lock(this) statement. This is basically what the Synchronized model does.
This article describes how to use the Synchronized model and how it differs from the lock(this) statement.
Comparing with lock(this)
Traditionally, the C# lock(this) statement has been used to synchronize access of several threads to a single object. When an object is locked by one thread, any other thread that attempts to access that object will have its execution blocked.
private object myLockingObject = new Object();
public void DoSomething()
{
lock ( myLockingObject )
{
// Some code that runs in one thread at a time.
Console.WriteLine( "Doing something." );
}
}
In this example, the myLockingObject member variable is used as a locking object. Once a thread runs the lock(myLockingObject) line, all other threads that enter the DoSomething method will stop executing, or be blocked, until the original thread has exited the lock(myLockingObject) code block.
The Synchronized model is similar to using the lock statement around every single public method, but it has the following differences:
It is not technically equivalent to locking the current instance (
this). Another object is actually being locked.Locking is automatic for all public and internal instance methods. You cannot forget it.
If a thread attempts to access a field without having first acquired access to the object (by invoking a public or internal method), an exception will be thrown.
The pattern also works with entities composed of several objects organized in a tree.
Applying the Synchronized model to a class
To apply the Synchronized threading model to a class:
Add the
PostSharp.Patterns.Threadingpackage to your project.Import the
PostSharp.Patterns.Threadingnamespace into your file.Add the SynchronizedAttribute to the class.
Annotate your object model for parent/child relationships as described in Annotating an Object Model for Parent/Child Relationships (Aggregatable).
Example
In the example below, the SynchronizedAttribute has been added to the class.
[Synchronized]
public class OrderService
{
public void Process( int sequence, ManualResetEventSlim? started = null )
{
Console.WriteLine( "sequence {0}: started", sequence );
started?.Set();
Thread.Sleep( 500 );
Console.WriteLine( "sequence {0}: completed", sequence );
}
}
To test this, we can run the following code.
static void Main()
{
var orderService = new OrderService();
// Call Process on a background thread, and wait until this thread has entered the method.
var started = new ManualResetEventSlim();
var backgroundTask = Task.Run( () => orderService.Process( 1, started ) );
started.Wait();
// Call Process on the main thread. This call waits until the background thread exits the method.
orderService.Process( 2 );
backgroundTask.Wait();
}
The code above executes the Process method on two different threads: a background thread and the main thread. The started event makes sure that the background thread enters the method first. Because both threads access the same instance of OrderService, the background thread blocks the main thread until it exits the Process method. The Thread.Sleep call does not let the main thread in. When the background thread exits the method, the main thread is no longer blocked and continues its execution.
The output of this program is:
sequence 1: started
sequence 1: completed
sequence 2: started
sequence 2: completed
Rules enforced by the Synchronized aspect
The SynchronizedAttribute aspect emits build-time errors in the following situations:
- The class contains a public or internal field.
A synchronized object will throw a ThreadAccessException whenever some code tries to access a field from a thread that does not own the correct lock, that is, the call stack does not contain a public or internal method of this object (for example, a private delegate call).
Working with object trees
Because the Synchronized model is an implementation of the Aggregatable pattern, all of the same behaviors of the AggregatableAttribute are available. For more information regarding object trees, see Parent/Child, Visitor and Disposable.
Note
Once you have established your parent-child relationships, you will need to apply compatible threading models to the child classes. You will want to refer to the Compatibility of Threading Models article to determine which threading model will work for the children of the Synchronized object.
See Also
Other Resources
Compatibility of Threading Models
Reference
SynchronizedAttribute
ChildAttribute
PrivateThreadAwareAttribute