Open sandboxFocusImprove this doc

THR016: Method of a Reader/Writer Synchronized Class Has No Access Level

Severity Warning
Reported by PostSharp.Patterns.Threading
Message Method "<Method>" must be annotated with the [Reader], [UpgradeableReader] or [Writer] custom attribute because the class "<Class>" has the [ReaderWriterSynchronized] aspect and the method can be invoked from outside the object. Without one of these attributes, the method acquires no lock.

Cause

The ReaderWriterSynchronizedAttribute aspect lets several threads read an object at the same time, but gives exclusive access to a thread that modifies it. PostSharp cannot tell whether a method only reads the object or modifies it, so you declare it with a custom attribute: ReaderAttribute, UpgradeableReaderAttribute or WriterAttribute, or another attribute derived from AccessLevelAttribute.

PostSharp reports THR016 for a method without such an attribute that can be invoked from outside the object: a public, internal or protected internal method, a method that implements an interface, or a method with the EntryPointAttribute custom attribute. Property accessors are not reported: a getter acquires the read lock and a setter the write lock.

The method then acquires no lock. When run-time verification is enabled, which is the default in the debug configuration, an access to a field of the object from this method throws a ThreadAccessException; otherwise, the method accesses the object without synchronization.

In the following example, the ChangePrice method of Product has no attribute.

using System;
using PostSharp.Patterns.Threading;

namespace Errors.THR016_Error;

[ReaderWriterSynchronized]
public class Product
{
    private decimal price;

    // PostSharp does not know which lock this method needs, so it acquires none.
    public void ChangePrice( decimal newPrice )
    {
        this.price = newPrice;
    }

    [Reader]
    public decimal GetPrice() => this.price;
}

public static class Program
{
    public static void Main()
    {
        var coffee = new Product();

        try
        {
            coffee.ChangePrice( 3.5m );
            Console.WriteLine( $"Price: {coffee.GetPrice()}" );
        }
        catch ( Exception e )
        {
            Console.WriteLine( $"{e.GetType().Name}: {e.Message}" );
        }
    }
}
warning THR016: THR016_Error.cs(12,17): Method "Errors.THR016_Error.Product.ChangePrice(System.Decimal)" must be annotated with the [Reader], [UpgradeableReader] or [Writer] custom attribute because the class "Errors.THR016_Error.Product" has the [ReaderWriterSynchronized] aspect and the method can be invoked from outside the object. Without one of these attributes, the method acquires no lock.

Because the method does not acquire the write lock, assigning the field fails at run time:

ThreadAccessException: The current thread does not have the permission to access the object. Ensure the object entry point (i.e. the public/internal method or the delegate callback method) is annotated with the [Writer] custom attribute.

Resolution

Annotate the method with the attribute of the access it needs:

To annotate every method of the class at once, apply the attribute to the class. When the method synchronizes its accesses by itself, or does not access the object, apply the ExplicitlySynchronizedAttribute custom attribute to it instead.

In the following example, ChangePrice acquires the write lock.

using System;
using PostSharp.Patterns.Threading;

namespace Errors.THR016_Fixed;

[ReaderWriterSynchronized]
public class Product
{
    private decimal price;

    // The method modifies the object, so it acquires the write lock.
    [Writer]
    public void ChangePrice( decimal newPrice )
    {
        this.price = newPrice;
    }

    [Reader]
    public decimal GetPrice() => this.price;
}

public static class Program
{
    public static void Main()
    {
        var coffee = new Product();

        try
        {
            coffee.ChangePrice( 3.5m );
            Console.WriteLine( $"Price: {coffee.GetPrice()}" );
        }
        catch ( Exception e )
        {
            Console.WriteLine( $"{e.GetType().Name}: {e.Message}" );
        }
    }
}
Price: 3.5

See Also

Reference

ReaderWriterSynchronizedAttribute
ReaderAttribute
WriterAttribute
UpgradeableReaderAttribute

Other Resources

Reader/Writer Synchronized Threading Model
Enabling and Disabling Run-Time Verification