none
locking with Monitor across AppDomain RRS feed

  • General discussion

  • I recently had to synchronize threads in a process but possibly in different AppDomains. So I thought of a named Mutex, but while I was poking around MSDN, I noticed this sentence about Monitors from "Overview of Synchronization Primitives":

    The Monitor class can provide locking in multiple application domains if the object used for the lock derives from MarshalByRefObject.

    My main question is, what does this mean exactly? It sounds to me like if I have an object that derives from MarshalByRefObject, then I can pass an instance from one AppDomain to another and then do "lock(obj) {}" from two different AppDomains. I've tried, and this doesn't work.

    So what does the sentence mean?


    • Changed type Fred BaoModerator Monday, October 6, 2014 3:40 AM The discussion type is more suitable for this case
    Monday, September 15, 2014 2:11 PM

All replies

  • Hello Philip,

    >>My main question is, what does this mean exactly?

    As far as I know, the Monitor class controls access to objects by granting a lock for an object to a single thread, it does not care if the object is used in different AppDomain, it pays attention to the thread, as we know, a thread could run between different appdomains so I think this sentence means it would prevent other thread access the blocked object even being used in other AppDomain.

    >>I've tried, and this doesn't work.

    Please check if the two AppDoamins are use the same thread, if it is, this should be a correct behavior.

    If not, please share your code with us.

    If i misunderstand, please let me know.

    Best Regards,

    Fred.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Tuesday, September 16, 2014 6:06 AM
    Moderator
  • Thanks for the reply. Below is my code. It should be pretty easy to try it out and see what results you get.

    using System;
    using System.Threading;
    using System.Windows.Forms;
    using System.Diagnostics;

    namespace AppDomainTest
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }

            private EventWaitHandle _start = new EventWaitHandle(false, EventResetMode.ManualReset, "START_EVENT");

            private void button1_Click(object sender, EventArgs e)
            {
                AppDomain otherDomain = AppDomain.CreateDomain("other_domain");
                SillyMBRO lockObj = new SillyMBRO();
                otherDomain.SetData("LOCKOBJ", lockObj);
                AppDomain.CurrentDomain.SetData("LOCKOBJ", lockObj);

                ThreadPool.QueueUserWorkItem(state => otherDomain.DoCallBack(new CrossAppDomainDelegate(AppDomainCallback)));
                ThreadPool.QueueUserWorkItem(state => AppDomainCallback());

                Thread.Sleep(1000);
                _start.Set();
            }

            public static void AppDomainCallback()
            {
                object lockObj = AppDomain.CurrentDomain.GetData("LOCKOBJ");

                EventWaitHandle.OpenExisting("START_EVENT").WaitOne();
                const int reallyBigNumber = 10000; //int.MaxValue/100;
                lock (lockObj)
                {
                    for (int i = 0; i < reallyBigNumber; i++)
                    {
                        Trace.WriteLine(
                            string.Format("AppDomainCallback: AppDomain is {0} i is {2} thread is {1}",
                                AppDomain.CurrentDomain.FriendlyName, Thread.CurrentThread.ManagedThreadId, i));
                    }
                }
            }
        }

        internal class SillyMBRO : MarshalByRefObject
        {
        }
    }

    Tuesday, September 16, 2014 11:25 AM
  • Hello Philip,

    I do know what result you get, with your provide code, the test result is that the main thread would walk over the 10000 times loop and then the sub thread walk over 10000 times loop. I think this should be ok. So what does it confuse you?

    Here I make a sample which shows it locks in multiple application domains if the object used for the lock derives from MarshalByRefObject.

    class Program
    
        {
    
            static void Main(string[] args)
    
            {
    
                #region MyRegion
    
                Console.WriteLine("Thread Start " + Thread.CurrentThread.ManagedThreadId);
    
                int mainThreadId = Thread.CurrentThread.ManagedThreadId;
    
                string assembly = Assembly.GetEntryAssembly().FullName;
    
                AppDomain otherDomain = AppDomain.CreateDomain("other_domain");
    
                RemoteObject ro = null;
    
                ro = (RemoteObject)otherDomain.CreateInstanceAndUnwrap(assembly, "P20140916.RemoteObject");
    
                Thread newThread = new Thread(new ThreadStart(() => ro.Print(mainThreadId)));
    
                newThread.Start();
    
                ro.Print(mainThreadId);
    
                #endregion
    
            }
    
        }
    
    
        public class RemoteObject : System.MarshalByRefObject
    
        {
    
            public RemoteObject()
    
            {
    
    
            }
    
    
            public void Print(int threadId)
    
            {
    
                if (Thread.CurrentThread.ManagedThreadId == threadId)
    
                {
    
                    Thread.Sleep(5000);
    
                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "sleep over and trys to access the blocked code");
    
                }
    
                lock (this)
    
                {
    
                    if (Thread.CurrentThread.ManagedThreadId == threadId)
    
                    {
    
                        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "comes into the blocked code");
    
                    }
    
    
                    if (Thread.CurrentThread.ManagedThreadId != threadId)
    
                    {
    
                        int i = 0;
    
                        while (i < 10)
    
                        {
    
                            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "is in blocked code");
    
                            Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
    
                            Thread.Sleep(1000);
    
                            i++;
    
                        }
    
                        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "goes out the blocked code");
    
                    }
    
                }
    
            }
    
        }
    

    If you run the code, you could see the object created from another appdomian would be locked by its touched thread.

    If i misunderstanding, please feel free let me know.

    Regards


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, September 17, 2014 9:29 AM
    Moderator
  • Thanks. However, when I run your code, the two threads are both running in "other_domain", so the lock(this) works, as expected. In my code sample, I spawn one thread in other_domain and the other thread in the default domain, and the lock(lockObj) is not providing mutual exclusion for the two domains. One thread is locking on the SillyMBRO and another is locking on a __TransparentProxy of the SillyMBRO. This is why the lock isn't working, but I'm just trying to figure out why the docs seem to say it should.

    If you add this line to your RemoteObject.Print method at the beginning, maybe you can see what I mean:

    Console.WriteLine("RemoteObject.Print called from domain " + AppDomain.CurrentDomain.FriendlyName);

    You will see "other_domain" is printed both times.

    When you run my code sample, don't you see interleaved Trace statements from both domains?

    Wednesday, September 17, 2014 3:21 PM
  • Hello,

    >> When you run my code sample, don't you see interleaved Trace statements from both domains?

    I am not sure if the run result in my side is same with yours, however, I only see the two threads run in one domain as:

    AppDomainCallback: AppDomain is P20140917.vshost.exe i is 0 thread is 12
    
    AppDomainCallback: AppDomain is P20140917.vshost.exe i is 1 thread is 12
    
    AppDomainCallback: AppDomain is P20140917.vshost.exe i is 0 thread is 6
    
    AppDomainCallback: AppDomain is P20140917.vshost.exe i is 1 thread is 6

    Even I increase the reallyBigNumber to 10000, it is that one thread would walk over 10000 firstly and then the second one starts. So I think the object should be locked. What is your result?

    If yours is similar, I do know why you think the object is not locked because if it is not locked, the two threads would be printed alternately:

    AppDomainCallback: AppDomain is P20140917.vshost.exe i is 0 thread is 12
    
    AppDomainCallback: AppDomain is P20140917.vshost.exe i is 1 thread is 6
    
    AppDomainCallback: AppDomain is P20140917.vshost.exe i is 1 thread is 12
    
    AppDomainCallback: AppDomain is P20140917.vshost.exe i is 0 thread is 6

    And the reason why the domain name would only show the P20140917.vshost.exe, it is because as you also said that it would create a transparent proxy, when executing a method based on the transparent proxy, this proxy would cause the thread to go to the actual domain which holds this object. Because the lockObj is declared in P20140917.vshost.exe, so that it would always show P20140917.vshost.exe.

    Regards,

    Fred.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.


    Thursday, September 18, 2014 9:57 AM
    Moderator
  • I am not sure why we're getting different results for my code sample. This line

    otherDomain.DoCallBack(new CrossAppDomainDelegate(AppDomainCallback))

    should execute AppDomainCallback in the other_domain, yet you're seeing "P20140917.vshost.exe"??

    Thursday, September 18, 2014 9:39 PM
  • Hello,

    You mean you get a different result, this is confused. There is the screenshot:

    You could see that the thread would walk one by one in a order and in the same domain.

    Regards.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.


    Friday, September 19, 2014 7:05 AM
    Moderator
  • Are you sure you copied all my code? Including these lines:

    ThreadPool.QueueUserWorkItem(state => otherDomain.DoCallBack(new CrossAppDomainDelegate(AppDomainCallback)));
                ThreadPool.QueueUserWorkItem(state => AppDomainCallback());

    Monday, September 22, 2014 7:59 PM
  • Hello,

    >> Are you sure you copied all my code? Including these lines:

    After comparing the copied code, these lines “ThreadPool.QueueUserWorkItem(state => otherDomain.DoCallBack(new CrossAppDomainDelegate(AppDomainCallback)));

                ThreadPool.QueueUserWorkItem(state => AppDomainCallback());” are included, and there is a difference is that I remove this static modifier of the AppDomainCallback method somehow. After adding it, yes, I reproduce that two threads shows alternately, this is confused. I notice that in the other domain it would be a proxy object, since we do not know how exactly this lock works, i am not sure if the lock might treat the proxy as a different one somehow so that it is not locked.

    Regards.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.



    Wednesday, September 24, 2014 10:09 AM
    Moderator
  • Thanks for your patience and your replies. Would it be possible to show all your code? Thanks.
    Wednesday, September 24, 2014 2:05 PM
  • I believe the mystery may be cleared up by documentation on AppDomain.DoCallback (http://msdn.microsoft.com/en-us/library/system.appdomain.docallback(v=vs.110).aspx). It shows that if you use a non-static method as the callback AND the object the method belongs to derives from MarshalByRefObject, then the callback will always execute in the default domain. Since the method belongs to a windows Form object (and Form derives from MarshalByRefObject -- ultimately), then that explains the behavior.
    Wednesday, September 24, 2014 3:50 PM
  • Hi Philip,

    Here is my original code and you could see the only difference is that I lost the static modifier:

    public partial class Form1 : Form
    
        {
    
            public Form1()
    
            {
    
                InitializeComponent();
    
            }
    
    
            private EventWaitHandle _start = new EventWaitHandle(false, EventResetMode.ManualReset, "START_EVENT");
    
    
            private void button1_Click(object sender, EventArgs e)
    
            {
    
                AppDomain otherDomain = AppDomain.CreateDomain("other_domain");
    
                SillyMBRO lockObj = new SillyMBRO();
    
                otherDomain.SetData("LOCKOBJ", lockObj);
    
                AppDomain.CurrentDomain.SetData("LOCKOBJ", lockObj);
    
    
                ThreadPool.QueueUserWorkItem(state => otherDomain.DoCallBack(new CrossAppDomainDelegate(AppDomainCallback)));
    
                ThreadPool.QueueUserWorkItem(state => AppDomainCallback());
    
    
                Thread.Sleep(1000);
    
                _start.Set();
    
            }
    
    
            public void AppDomainCallback()
    
            {
    
                object lockObj = AppDomain.CurrentDomain.GetData("LOCKOBJ");
    
    
                EventWaitHandle.OpenExisting("START_EVENT").WaitOne();
    
                const int reallyBigNumber = 10; //int.MaxValue/100;
    
                lock (lockObj)
    
                {
    
                    for (int i = 0; i < reallyBigNumber; i++)
    
                    {
    
                        Trace.WriteLine(
    
                            string.Format("AppDomainCallback: AppDomain is {0} i is {2} thread is {1}",
    
                                AppDomain.CurrentDomain.FriendlyName, Thread.CurrentThread.ManagedThreadId, i));
    
                    }
    
                }
    
            }
    
        }
    
    
        internal class SillyMBRO : MarshalByRefObject
    
        {
    
        }
    

    And thank you for sharing the experience with us. Others who have the same confusion would be glad to see this.

    Fred.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Thursday, September 25, 2014 6:22 AM
    Moderator
  • Thanks.

    So to wrap up, is the conclusion that you cannot lock on a __TransparentProxy of a MarshalByRefObject and expect it to provide locking across AppDomains? That looks like correct behavior. However, if anyone else has any other interpretation of the documentation's remarks of "The Monitor class can provide locking in multiple application domains if the object used for the lock derives from MarshalByRefObject", I'd enjoy to know it.


    Thursday, September 25, 2014 1:22 PM