diff --git a/samples/Samples.Mvc5/Controllers/HomeController.cs b/samples/Samples.Mvc5/Controllers/HomeController.cs index a176a204a..bb6a45224 100644 --- a/samples/Samples.Mvc5/Controllers/HomeController.cs +++ b/samples/Samples.Mvc5/Controllers/HomeController.cs @@ -10,6 +10,8 @@ using Samples.Mvc5.EfModelFirst; using Samples.Mvc5.EFCodeFirst; using Samples.Mvc5.Helpers; +using System.Threading.Tasks; +using System.Collections.Generic; namespace Samples.Mvc5.Controllers { @@ -131,6 +133,34 @@ public ActionResult About() /// The . public ActionResult ResultsAuthorization() => View(); + public async Task CallAsync() + { + var profiler = MiniProfiler.Current; + var service = new FooService(); + using (profiler.Step("action")) + { + var head = profiler.Head; + var tasks = new List(); + for (int i = 0; i < 10; i++) + { + tasks.Add(service.FooAsync(head)); + } + await Task.WhenAll(tasks); + return Content("All good"); + } + } + + class FooService + { + public async Task FooAsync(Timing head) + { + using (new Timing(MiniProfiler.Current, head, "foo async", minSaveMs: 100000)) + { + await Task.Delay(100).ConfigureAwait(false); + } + } + } + /// /// fetch the route hits. /// diff --git a/samples/Samples.Mvc5/Views/Home/Index.cshtml b/samples/Samples.Mvc5/Views/Home/Index.cshtml index 3eb0a9b5e..b40fea6f6 100644 --- a/samples/Samples.Mvc5/Views/Home/Index.cshtml +++ b/samples/Samples.Mvc5/Views/Home/Index.cshtml @@ -65,6 +65,7 @@
  • Massive Nesting 2
  • Parameterized SQL with Enums
  • Test Min Save Ms
  • +
  • Async call + Concurrency
  • diff --git a/src/MiniProfiler.Shared/Timing.cs b/src/MiniProfiler.Shared/Timing.cs index e3aa9839f..64d8c6a48 100644 --- a/src/MiniProfiler.Shared/Timing.cs +++ b/src/MiniProfiler.Shared/Timing.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Runtime.Serialization; using StackExchange.Profiling.Helpers; +using System.Threading; +using System.Threading.Tasks; #if NET45 using System.Web.Script.Serialization; #endif @@ -22,6 +24,9 @@ public class Timing : IDisposable private readonly decimal? _minSaveMs; private readonly bool _includeChildrenWithMinSave; + // used for concurrent access detection + private int _threadsAccessing = 0; + /// /// Initialises a new instance of the class. /// Obsolete - used for serialization. @@ -253,19 +258,43 @@ public void Stop() /// public void AddChild(Timing timing) { - if (Children == null) + if (Interlocked.Increment(ref _threadsAccessing) > 1) + throw new Exception("Concurrent access"); + + try + { + if (Children == null) Children = new List(); - Children.Add(timing); - if(timing.Profiler == null) - timing.Profiler = Profiler; - timing.ParentTiming = this; - timing.ParentTimingId = Id; - if (Profiler != null) - timing.MiniProfilerId = Profiler.Id; + Children.Add(timing); + if(timing.Profiler == null) + timing.Profiler = Profiler; + timing.ParentTiming = this; + timing.ParentTimingId = Id; + if (Profiler != null) + timing.MiniProfilerId = Profiler.Id; + } + finally + { + Interlocked.Decrement(ref _threadsAccessing); + } } - internal void RemoveChild(Timing timing) => Children?.Remove(timing); + internal void RemoveChild(Timing timing) + { + if (Interlocked.Increment(ref _threadsAccessing) > 1) + throw new Exception("Concurrent access"); + + Task.Delay(10).Wait(); + try + { + Children?.Remove(timing); + } + finally + { + Interlocked.Decrement(ref _threadsAccessing); + } + } /// /// Adds to this step's dictionary of