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?
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With