/// <summary> /// Assert to find possible deadlocks in code that uses async/await /// Throws an exception of type <see cref="ConcurrencyException"/> if any errors are found. /// </summary> /// <exception cref="ConcurrencyException"></exception> /// <param name="action">Delegate to execute</param> /// <param name="shouldEndBeforeMs">Maximum time in which the code should execute, this is used to determine deadlocks.</param> public static void AssertAsyncDeadlocksOnly(Action action, int shouldEndBeforeMs = 2000) { try { var cts = new CancellationTokenSource(shouldEndBeforeMs); ConcurrentRunner.Start(new[] { action }, 1, cts.Token); } catch (OperationCanceledException) { throw new ConcurrencyException("Possible deadlock detected. Make sure that you do not use .Wait(), .WaitAny(), .WaitAll(), .GetAwaiter().GetResult() or .Result on async methods."); } }
/// <summary> /// Will run the concurrency checker and will output a report if there were any errors. /// It will throw an exception of type <see cref="ConcurrencyException"/> if any errors are found. /// </summary> /// <param name="numberOfConcurrentThreads">Number of concurrent threads to run. The higher the number the more likely a problem will be found.</param> /// <param name="actions">Actions to execute concurrently</param> /// <example>ConcurrencyChecker.AssertRun(5, new Action(() => instance.ChangeNameTo("Jane")), new Action(() => instance.ChangeNameTo("Peter")))</example> /// <returns>Report if failed or null if successful</returns> public string Run(int numberOfConcurrentThreads, params Action[] actions) { if (numberOfConcurrentThreads < actions.Length) { throw new ArgumentOutOfRangeException(nameof(numberOfConcurrentThreads), "Number of concurrent threads are less than the number of actions to execute."); } _cts = new CancellationTokenSource(5000); try { InitializeMembersToMonitor(); } catch (OperationCanceledException) { return("Initialization failed. This normally happens is you are using a mock framework such as Moq. Please exclude the mock namespace (NSubstitute and Moq is supported out of the box)."); } _cts = new CancellationTokenSource(); Start(true); actions.First()(); Stop(); _cts = new CancellationTokenSource(MaxExecutionTimeMs); try { Start(); ConcurrentRunner.Start(actions, numberOfConcurrentThreads, _cts.Token); Stop(); return(GetReport()); } catch (OperationCanceledException) { return("Possible deadlock detected. Make sure that you do not use .Wait() or .Result on async methods."); } }