1

Simple question, but docs are silent. In sources there are some locks, so probably it is, but I want to know for sure.

Maybe there is an existing answer before I attempted to write a test? If none, how would such test looks like (I am not sure what exactly I have to test to answer if it's thread-safe or not)?

Background: I am using CSharpScript to run user scripts where globals will contain dynamic properties (ExpandoObject instances). Those properties will contain used in script methods (Action<T>), added and removed dynamically from another thread. This will provide user with nicer syntax: ExpandoProperty.BlaBlaBla(parameters); compared to using e.g. dictionary: DictionaryProperty["BlaBlaBla"](parameters);

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • If you are planning to delete some actions from expando object, then how your calling code will know what they can call? – Aleksandr Ivanov Apr 16 '19 at 15:06
  • @AleksandrIvanov, the script will use `dynamic`, if it's not exists it will crash. I am ok with crash when method is not there and script is trying to call it, but it should not crash when e.g. unused by script method is deleted. – Sinatr Apr 16 '19 at 15:09
  • So you mean that thread 1 can call a method and thread 2 can delete this method at the same time? – Aleksandr Ivanov Apr 16 '19 at 15:35

2 Answers2

2

ExpandoObject implements the IDictionary<string,Object> collection interface and as you looked at the implementation then it does have internal methods TryGetValue, TryAddValue, TryAddMember and TryDeleteValue similar to ConcurrentDictionary to add dynamic properties and values. Also it does use the locking mechanism for collection items update operations. It seems thread safe to me.

vendettamit
  • 14,315
  • 2
  • 32
  • 54
0

If you started to execute a method on expando object in one thread and remove this method in other thread, then it works fine:

dynamic a = new ExpandoObject();
a.Action1 = (Action)(() => 
{ 
    Console.WriteLine("Action1 Start"); 
    Thread.Sleep(1000); 
    Console.WriteLine("Action1 End");
});



Task task1 = Task.Factory.StartNew(() => 
{ 
    a.Action1(); 
});

Task task2 = Task.Factory.StartNew(() => 
{ 
    ((IDictionary<String, Object>)a).Remove("Action1");
    Console.WriteLine("Action1 Removed");
});


Task.WaitAll(task1, task2);

Here is another example when method is constantly added and removed from expando object in one thread and called in other thread. The exception thrown here only when method is missing:

dynamic a = new ExpandoObject();

Task task1 = Task.Factory.StartNew(() =>
{
    while(true) 
    {
        a.Action1 = (Action)(() => { Thread.Sleep(10); });
        Thread.Sleep(100);
        ((IDictionary<String, Object>)a).Remove("Action1");
        Thread.Sleep(100);
    }
});

Task task2 = Task.Factory.StartNew(() => 
{
    while(true)
    {
        try 
        {
            a.Action1();
            Console.WriteLine("Action1");
            Thread.Sleep(20);
        }
        catch(Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
});

Of course you will need to verify that code inside expando object method is thread safe.

Aleksandr Ivanov
  • 2,778
  • 5
  • 27
  • 35
  • Good idea, I had nearly the same, just haven't thought to simply display all exceptions and look by eyes if there is any unexpected. Another plus I haven't thought to use `dynamic` to add methods like you do, was casting to `IDictionary` instead to call `Add` method, silly me, thanks. – Sinatr Apr 17 '19 at 10:31