Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cost of ExecutionContext.Capture() and ExecutionContext.Run(context, work, state)

Does anybody know if it is expensive to ExecutionContext.Capture() and to ExecutionContext.Run(context, work, state)?

Does it decrease performance and so is recommended to use carefully?

I'm asking since I have an ContextItem where I save Context work and state in to execute later. Since I want to be able to react on an exception that might be thrown while executing work, I have a fallback that is executed if an exceptrion is thrown in work. And I also have final work, that is executed in any case regardlessly if an exception was thrown or not. Since I can use an ExecutionContext only once I would have to ExecutionContext.Capture() three times for one of these ContextItems...

Or does this sound like a totally wrong approach?

like image 934
fose Avatar asked Nov 09 '22 22:11

fose


1 Answers

As recommended by @Alois Kraus I ran a test with the following code comparing locking to capturing & lined up excecution:

class Program
{
    private static readonly object _lock = new object();
    private static readonly int numberOfItems = 1000000;
    private static readonly int _numberOfIterations = 1000000;

    private static void Main(string[] args)
    {
        MeasureTimeWithLocking();
        MeasureTimeWithCapuringContext();
        Console.WriteLine();
        MeasureTimeWithLocking();
        MeasureTimeWithCapuringContext();
        Console.WriteLine();
        MeasureTimeWithLocking();
        MeasureTimeWithCapuringContext();
        Console.ReadKey();
    }

    private static void MeasureTimeWithLocking()
    {
        List<ContextItem> items = new List<ContextItem>();
        Stopwatch stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < numberOfItems; i++)
        {
            ContextItem item = new ContextItem();
            item.Work1 = DoSomeWorkWithLock;
            item.Work2 = DoSomeWorkWithLock;
            item.Work3 = DoSomeWorkWithLock;
        }

        Parallel.ForEach(items, (item) =>
        {
            item.Work1(null);
            item.Work2(null);
            item.Work3(null);
        });
        stopwatch.Stop();
        Console.WriteLine("Time elapsed with locking:           " + stopwatch.Elapsed);
    }

    private static void MeasureTimeWithCapuringContext()
    {
        List<ContextItem> items = new List<ContextItem>();
        Stopwatch stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < numberOfItems; i++)
        {
            ContextItem item = new ContextItem();
            item.Context1 = ExecutionContext.Capture();
            item.Context2 = ExecutionContext.Capture();
            item.Context3 = ExecutionContext.Capture();
            item.Work1 = DoSomeWork;
            item.Work2 = DoSomeWork;
            item.Work3 = DoSomeWork;
        }

        foreach (ContextItem item in items)
        {
            ExecutionContext.Run(item.Context1, item.Work1, null);
            ExecutionContext.Run(item.Context2, item.Work2, null);
            ExecutionContext.Run(item.Context3, item.Work3, null);
        }
        stopwatch.Stop();
        Console.WriteLine("Time elapsed with capturing context: " + stopwatch.Elapsed);
    }

    private static void DoSomeWork(object ignored)
    {
        Work();
    }


    private static void DoSomeWorkWithLock(object ignored)
    {
        lock (_lock)
        {
            Work();
        }
    }

    private static void Work()
    {
        int count = 0;
        for (int i = 0; i < _numberOfIterations; i++)
        {
            count ++;
        }
    }

    private class ContextItem
    {
        public ExecutionContext Context1 { get; set; }
        public ExecutionContext Context2 { get; set; }
        public ExecutionContext Context3 { get; set; }

        public ContextCallback Work1 { get; set; }
        public ContextCallback Work2 { get; set; }
        public ContextCallback Work3 { get; set; }
    }
}

Results are:

enter image description here

So if I did this right, capturing & executing lined up is in average round about 5 times more expensive than locking.

To also answer the part of my question:

Or does this sound like a totally wrong approach?

I read in this article that

if you have to know they’re there, either you’re doing something super advanced, or something’s gone wrong.

The article was recommended on SO as the best source if you want to know about ExecutionContext. After going through it and running some tests with a colleague I realized that I was using ExecutionContext where it didn’t make sense, plus it is less performant then than locks and so it probably also is less performant than other threading functionalities / constructs.

like image 75
fose Avatar answered Nov 14 '22 21:11

fose