简体   繁体   中英

C# prevent access to all object methods from other threads

I have an object that have to be used by only a single Thread at a time. For example my Object contains 3 methods A , B and C and I want to lock the object (all the methods/attributes are locked) if a Thread access the method A .

The main difficultie is that I can't modify the code of that object. I have to prevent multithreads access where i'm calling the object.

My first thought was to use the singleton pattern but i didn't manage to make it work!

If you can't change the code of the object, you'll have to handle the locking outside the object. For example, you could encapsulate it in another class (maybe hiding it behind an interface), and have that wrapper class apply the synchronization:

public class Foo {
    private readonly YourType tail;
    private readonly object syncLock = new object();
    public Foo(YourType tail) {this.tail = tail;}

    public A() { lock(syncLock) { tail.A(); } }
    public B() { lock(syncLock) { tail.B(); } }
    public C() { lock(syncLock) { tail.C(); } }
}

Singleton pattern isn't the right one here - it ensures that there's only a single instance of an object, but doesn't dictate how you can use it.

Thread safety of code has to be defined within that code . That is, if you can't modify the code of your object, you won't be able to make it properly thread-safe. However, there's a workaround: you can wrap the object in a new class that you create, and make sure your new object is thread-safe. By exposing thread-safe wrappers for you unsafe object's methods, you can ensure that it's accessed the way you want it to be.

The easiest way to do this is to use the lock keyword. Something like this might work:

public class ThreadSafeThing
{
    private UnsafeThing _thing = new UnsafeThing();
    private object _syncRoot = new object();

    public void DoSomething()   // this is your thread-safe version of Thing.DoSomething
    {
        lock (_syncRoot)
        {
            _thing.DoSomething();
        }
    }
}

The OP didn't specify, but if his scenerio includes the possibility that he needs to keep the object locked across multiple client calls (eg he needs to call function A from the client, then depending on the result, call function B or C, keeping the object locked to other threads the entire time) you would need to implement a bit differently, example:

public static class ThreadSafeThing {
    private static UnsafeThing _thing = new UnsafeThing();
    private static readonly object _lock = new object();

    public static void getLock() {
        Monitor.Enter(_lock);
    }

    public static void releaseLock() {
        Monitor.Exit(_lock);
    }

    // this is your thread-safe version of Thing.DoSomething             
    public static bool DoSomething() {
        try {
            Monitor.Enter(_lock);
            return _thing.DoSomething();
        }
        finally {
            Monitor.Exit(_lock);
        }
    }

    // this is your thread-safe version of Thing.DoSomethingElse
    public static void DoSomethingElse() {
        try {
            Monitor.Enter(_lock);
            return _thing.DoSomethingElse();
        }
        finally {
            Monitor.Exit(_lock);
        }
    }
}

From the client call like so...

try {
    ThreadSafeThing.getLock();
    if (ThreadSafeThing.DoSomething()) {
        ThreadSafeThing.DoSomethingElse();
    } 
}
finally {
    // This must be called no matter what happens
    ThreadSafeThing.releaseLock();
}

The main difference here is that the client is responsible for obtaining a lock and releasing it once it is finished. This allows calls to multiple functions across the object while maintaining the lock. All other threads will block on the getLock call until the lock is released using releaseLock.

EDIT: Added auto-obtaining a lock in the DoSomething and DoSomethingElse methods allowing a thread to also obtain a single-use lock if directly calling these methods without first obtaining a lock via the getLock method. It should be noted, however, that if the lock is obtained this way, it only lasts for the single method call.

Assuming that you cannot just create one object per thread, the other way is to raise one more thread to call the methods of the non-threadsafe object and then queue call-requests to that one thread. Usually, the thread should fire a 'OnCompletion' callback supplied in the queued requestafter it has performed the requsted operation on the non-threadsafe object.

The operations are then performed asynchronously, but you can make a synchronous call by queueing up the request and then waiting on an event that is signaled by the callback.

..just another possiblity that is more flexible than simple locking in a wrapper object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM