diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings
index 061db721683..307393c9ab8 100644
--- a/Avalonia.sln.DotSettings
+++ b/Avalonia.sln.DotSettings
@@ -22,6 +22,7 @@
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ UI
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="False" Prefix="I" Suffix="" Style="AaBb" />
diff --git a/build/XUnit.props b/build/XUnit.props
index 8f991ae4425..b4e9708ecde 100644
--- a/build/XUnit.props
+++ b/build/XUnit.props
@@ -1,14 +1,14 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
$(MSBuildThisFileDirectory)\avalonia.snk
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
index bcb60830b2c..21b1ee8f3aa 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
@@ -270,4 +271,25 @@ public bool HasJobsWithPriority(DispatcherPriority priority)
lock (InstanceLock)
return _queue.MaxPriority >= priority;
}
+
+ ///
+ /// Gets all pending jobs, unordered, without removing them.
+ ///
+ /// Only use between unit tests!
+ /// A list of jobs.
+ internal List GetJobs()
+ {
+ lock (InstanceLock)
+ return _queue.PeekAll();
+ }
+
+ ///
+ /// Clears all pending jobs.
+ ///
+ /// Only use between unit tests!
+ internal void ClearJobs()
+ {
+ lock (InstanceLock)
+ _queue.Clear();
+ }
}
diff --git a/src/Avalonia.Base/Threading/DispatcherOperation.cs b/src/Avalonia.Base/Threading/DispatcherOperation.cs
index e72add9d5c1..3bb28a17fed 100644
--- a/src/Avalonia.Base/Threading/DispatcherOperation.cs
+++ b/src/Avalonia.Base/Threading/DispatcherOperation.cs
@@ -1,12 +1,13 @@
using System;
using System.ComponentModel;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
-using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Threading;
+[DebuggerDisplay("{DebugDisplay}")]
public class DispatcherOperation
{
protected readonly bool ThrowOnUiThread;
@@ -25,7 +26,7 @@ public DispatcherPriority Priority
}
}
- protected object? Callback;
+ protected internal object? Callback;
protected object? TaskSource;
internal DispatcherOperation? SequentialPrev { get; set; }
@@ -53,6 +54,16 @@ private protected DispatcherOperation(Dispatcher dispatcher, DispatcherPriority
Dispatcher = dispatcher;
}
+ internal string DebugDisplay
+ {
+ get
+ {
+ var method = (Callback as Delegate)?.Method;
+ var methodDisplay = method is null ? "???" : method.DeclaringType + "." + method.Name;
+ return $"{methodDisplay} [{Priority}]";
+ }
+ }
+
///
/// An event that is raised when the operation is aborted or canceled.
///
diff --git a/src/Avalonia.Base/Threading/DispatcherPriorityQueue.cs b/src/Avalonia.Base/Threading/DispatcherPriorityQueue.cs
index 524b4fab8db..f7ddaab5b15 100644
--- a/src/Avalonia.Base/Threading/DispatcherPriorityQueue.cs
+++ b/src/Avalonia.Base/Threading/DispatcherPriorityQueue.cs
@@ -398,6 +398,23 @@ private void RemoveItemFromSequentialChain(DispatcherOperation item)
// Step 3: cleanup
item.SequentialPrev = item.SequentialNext = null;
}
+
+ public List PeekAll()
+ {
+ var operations = new List();
+
+ for (var item = _head; item is not null; item = item.SequentialNext)
+ operations.Add(item);
+
+ return operations;
+ }
+
+ public void Clear()
+ {
+ _priorityChains.Clear();
+ _cacheReusableChains.Clear();
+ _head = _tail = null;
+ }
}
@@ -415,4 +432,4 @@ public PriorityChain(DispatcherPriority priority) // NOTE: should be Priority
public DispatcherOperation? Head { get; set; }
public DispatcherOperation? Tail { get; set; }
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs
index b1ca75d0180..646e33a841e 100644
--- a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs
@@ -15,7 +15,7 @@
namespace Avalonia.Base.UnitTests.Composition;
-public class CompositionAnimationTests
+public class CompositionAnimationTests : ScopedTestBase
{
class AnimationDataProvider : DataAttribute
@@ -114,4 +114,4 @@ public override string ToString()
return Name;
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs b/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
index ed1061e85a9..968e2397e5a 100644
--- a/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
@@ -7,7 +7,7 @@
namespace Avalonia.Base.UnitTests.Input
{
- public class AccessKeyHandlerTests
+ public class AccessKeyHandlerTests : ScopedTestBase
{
[Fact]
public void Should_Raise_Key_Events_For_Unregistered_Access_Key()
diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
index b4d20f2496a..224dceaf729 100644
--- a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
@@ -14,7 +14,7 @@
namespace Avalonia.Base.UnitTests.Input;
-public abstract class PointerTestsBase
+public abstract class PointerTestsBase : ScopedTestBase
{
private protected static void SetHit(Mock renderer, Control? hit)
{
diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs
index 09e4986da02..8ec87484f0e 100644
--- a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs
+++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs
@@ -11,7 +11,7 @@
namespace Avalonia.Base.UnitTests.Layout
{
- public class LayoutableTests_EffectiveViewportChanged
+ public class LayoutableTests_EffectiveViewportChanged : ScopedTestBase
{
[Fact]
public async Task EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree_And_Layout_Pass_Has_Not_Run()
@@ -38,9 +38,7 @@ await RunOnUIThread.Execute(async () =>
[Fact]
public async Task EffectiveViewportChanged_Raised_When_Control_Added_To_Tree_And_Layout_Pass_Has_Run()
{
-#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
await RunOnUIThread.Execute(async () =>
-#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
var root = CreateRoot();
var target = new Canvas();
@@ -64,9 +62,7 @@ await RunOnUIThread.Execute(async () =>
[Fact]
public async Task EffectiveViewportChanged_Raised_When_Root_LayedOut_And_Then_Control_Added_To_Tree_And_Layout_Pass_Runs()
{
-#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
await RunOnUIThread.Execute(async () =>
-#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
var root = CreateRoot();
var target = new Canvas();
diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs
index 77f1a8882d0..866d7cbb759 100644
--- a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs
+++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs
@@ -6,7 +6,7 @@
namespace Avalonia.Base.UnitTests.Layout
{
- public class LayoutableTests_LayoutRounding
+ public class LayoutableTests_LayoutRounding : ScopedTestBase
{
[Theory]
[InlineData(100, 100)]
@@ -112,7 +112,7 @@ private static void AssertEqual(Point expected, Point actual)
{
if (!expected.NearlyEquals(actual))
{
- throw new EqualException(expected, actual);
+ throw EqualException.ForMismatchedValues(expected, actual);
}
}
@@ -120,7 +120,7 @@ private static void AssertEqual(Size expected, Size actual)
{
if (!expected.NearlyEquals(actual))
{
- throw new EqualException(expected, actual);
+ throw EqualException.ForMismatchedValues(expected, actual);
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs b/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
index 7d45a770b0a..c3cffd360ab 100644
--- a/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
+++ b/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
@@ -1,7 +1,9 @@
using System.Reflection;
+using Avalonia.UnitTests;
using Xunit;
-[assembly: AssemblyTitle("Avalonia.UnitTests")]
+[assembly: AssemblyTitle("Avalonia.Base.UnitTests")]
// Don't run tests in parallel.
[assembly: CollectionBehavior(DisableTestParallelization = true)]
+[assembly: VerifyEmptyDispatcherAfterTest]
diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
index 22cafb403bf..413ce22e715 100644
--- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
@@ -358,8 +358,10 @@ public void Close_Window_On_Alt_F4_When_ComboBox_Is_Focus()
}
[Fact]
- public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight()
+ public void FlowDirection_Of_RectangleContent_Should_Be_LeftToRight()
{
+ using var app = UnitTestApplication.Start(TestServices.StyledWindow);
+
var target = new ComboBox
{
FlowDirection = FlowDirection.RightToLeft,
@@ -385,6 +387,8 @@ public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight()
[Fact]
public void FlowDirection_Of_RectangleContent_Updated_After_InvalidateMirrorTransform()
{
+ using var app = UnitTestApplication.Start(TestServices.StyledWindow);
+
var parentContent = new Decorator()
{
Child = new Control()
diff --git a/tests/Avalonia.Controls.UnitTests/Properties/AssemblyInfo.cs b/tests/Avalonia.Controls.UnitTests/Properties/AssemblyInfo.cs
index 4a32235f924..3b191d375e2 100644
--- a/tests/Avalonia.Controls.UnitTests/Properties/AssemblyInfo.cs
+++ b/tests/Avalonia.Controls.UnitTests/Properties/AssemblyInfo.cs
@@ -1,7 +1,9 @@
using System.Reflection;
+using Avalonia.UnitTests;
using Xunit;
[assembly: AssemblyTitle("Avalonia.Controls.UnitTests")]
// Don't run tests in parallel.
-[assembly: CollectionBehavior(DisableTestParallelization = true)]
\ No newline at end of file
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
+[assembly: VerifyEmptyDispatcherAfterTest]
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index 4189ecd9de0..a102ca95f3d 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -408,6 +408,8 @@ public void SelectedContentTemplate_Updates_After_New_ContentTemplate()
[Fact]
public void Previous_ContentTemplate_Is_Not_Reused_When_TabItem_Changes()
{
+ using var app = UnitTestApplication.Start(TestServices.StyledWindow);
+
int templatesBuilt = 0;
var target = new TabControl
diff --git a/tests/Avalonia.Headless.UnitTests/InputTests.cs b/tests/Avalonia.Headless.UnitTests/InputTests.cs
index f96606b5bb4..841201e51e5 100644
--- a/tests/Avalonia.Headless.UnitTests/InputTests.cs
+++ b/tests/Avalonia.Headless.UnitTests/InputTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Reactive.Disposables;
using System.Threading;
+using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Layout;
@@ -35,12 +36,11 @@ public InputTests()
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
- [AvaloniaFact(Timeout = 10000)]
+ [AvaloniaFact]
#endif
public void Should_Click_Button_On_Window()
{
Assert.True(_setupApp == Application.Current);
-
var buttonClicked = false;
var button = new Button
{
@@ -62,7 +62,7 @@ public void Should_Click_Button_On_Window()
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
- [AvaloniaFact(Timeout = 10000)]
+ [AvaloniaFact]
#endif
public void Change_Window_Position()
{
diff --git a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs
index 74afb158658..7bb9e1c5b04 100644
--- a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs
+++ b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs
@@ -14,7 +14,7 @@ public class RenderingTests
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
- [AvaloniaFact(Timeout = 10000)]
+ [AvaloniaFact]
#endif
public void Should_Render_Last_Frame_To_Bitmap()
{
@@ -43,7 +43,7 @@ public void Should_Render_Last_Frame_To_Bitmap()
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
- [AvaloniaFact(Timeout = 10000)]
+ [AvaloniaFact]
#endif
public void Should_Not_Crash_On_GeometryGroup()
{
@@ -79,7 +79,7 @@ public void Should_Not_Crash_On_GeometryGroup()
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
- [AvaloniaFact(Timeout = 10000)]
+ [AvaloniaFact]
#endif
public void Should_Not_Crash_On_CombinedGeometry()
{
@@ -110,7 +110,7 @@ public void Should_Not_Crash_On_CombinedGeometry()
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
- [AvaloniaFact(Timeout = 10000)]
+ [AvaloniaFact]
#endif
public void Should_Not_Hang_With_Non_Trivial_Layout()
{
diff --git a/tests/Avalonia.Headless.UnitTests/ServicesTests.cs b/tests/Avalonia.Headless.UnitTests/ServicesTests.cs
index 251fe86ff56..8a6b03cf15a 100644
--- a/tests/Avalonia.Headless.UnitTests/ServicesTests.cs
+++ b/tests/Avalonia.Headless.UnitTests/ServicesTests.cs
@@ -13,7 +13,7 @@ public class ServicesTests
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
- [AvaloniaFact(Timeout = 10000)]
+ [AvaloniaFact]
#endif
public void Can_Access_Screens()
{
diff --git a/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs b/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs
index 9d5ef6e500b..4becb43b3b1 100644
--- a/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs
+++ b/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs
@@ -12,7 +12,7 @@ public class ThreadingTests
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
- [AvaloniaFact(Timeout = 10000)]
+ [AvaloniaFact]
#endif
public void Should_Be_On_Dispatcher_Thread()
{
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
index 1d7d767fb27..53b74f9694b 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
@@ -411,16 +411,16 @@ private static void AssertCloseEnough(PixelPoint expected, PixelPoint actual)
// the position of a centered window can be off by a bit. From initial testing, looks
// like this shouldn't be more than 10 pixels.
if (Math.Abs(expected.X - actual.X) > 10)
- throw new EqualException(expected, actual);
+ throw EqualException.ForMismatchedValues(expected, actual);
if (Math.Abs(expected.Y - actual.Y) > 10)
- throw new EqualException(expected, actual);
+ throw EqualException.ForMismatchedValues(expected, actual);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
if (Math.Abs(expected.X - actual.X) > 15)
- throw new EqualException(expected, actual);
+ throw EqualException.ForMismatchedValues(expected, actual);
if (Math.Abs(expected.Y - actual.Y) > 15)
- throw new EqualException(expected, actual);
+ throw EqualException.ForMismatchedValues(expected, actual);
}
else
{
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
index 4c0217bcc7b..bd63ddd4968 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
@@ -37,11 +37,11 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs b/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs
index 79e58f63e94..6d0d1f04886 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs
@@ -1,6 +1,8 @@
+using Avalonia.UnitTests;
using Xunit;
// Required to avoid InvalidOperationException sometimes thrown
// from Splat.MemoizingMRUCache.cs which is not thread-safe.
// Thrown when trying to access WhenActivated concurrently.
-[assembly: CollectionBehavior(DisableTestParallelization = true)]
\ No newline at end of file
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
+[assembly: VerifyEmptyDispatcherAfterTest]
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
index 67790789e31..d744f2ccacc 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
@@ -8,7 +8,7 @@
namespace Avalonia.ReactiveUI.UnitTests
{
- public class ReactiveUserControlTest
+ public class ReactiveUserControlTest : ScopedTestBase
{
public class ExampleViewModel : ReactiveObject, IActivatableViewModel
{
diff --git a/tests/Avalonia.UnitTests/InvariantCultureAttribute.cs b/tests/Avalonia.UnitTests/InvariantCultureAttribute.cs
index b21e364c1e7..d24acd23ec6 100644
--- a/tests/Avalonia.UnitTests/InvariantCultureAttribute.cs
+++ b/tests/Avalonia.UnitTests/InvariantCultureAttribute.cs
@@ -15,7 +15,7 @@ namespace Avalonia.UnitTests;
/// Some tests are formatting numbers, expecting a dot as a decimal point.
/// Use this fixture to set the current culture to the invariant culture.
///
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
public sealed class InvariantCultureAttribute : BeforeAfterTestAttribute
{
private CultureInfo? _previousCulture;
diff --git a/tests/Avalonia.UnitTests/VerifyEmptyDispatcherAfterTestAttribute.cs b/tests/Avalonia.UnitTests/VerifyEmptyDispatcherAfterTestAttribute.cs
new file mode 100644
index 00000000000..c4c0d299a5a
--- /dev/null
+++ b/tests/Avalonia.UnitTests/VerifyEmptyDispatcherAfterTestAttribute.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Avalonia.Controls;
+using Avalonia.Threading;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Avalonia.UnitTests;
+
+public sealed class VerifyEmptyDispatcherAfterTestAttribute : BeforeAfterTestAttribute
+{
+ public override void After(MethodInfo methodUnderTest)
+ {
+ if (typeof(ScopedTestBase).IsAssignableFrom(methodUnderTest.DeclaringType))
+ return;
+
+ var dispatcher = Dispatcher.UIThread;
+ var jobs = dispatcher.GetJobs();
+ if (jobs.Count == 0)
+ return;
+
+ dispatcher.ClearJobs();
+
+ // Ignore the Control.Loaded callback. It might happen synchronously or might be posted.
+ if (jobs.Count == 1 && IsLoadedCallback(jobs[0]))
+ return;
+
+ Assert.Fail(
+ $"The test left {jobs.Count} unprocessed dispatcher {(jobs.Count == 1 ? "job" : "jobs")}:\n" +
+ $"{string.Join(Environment.NewLine, jobs.Select(job => $" - {job.DebugDisplay}"))}\n" +
+ $"Consider using ScopedTestBase or UnitTestApplication.Start().");
+
+ static bool IsLoadedCallback(DispatcherOperation job)
+ => job.Priority == DispatcherPriority.Loaded &&
+ (job.Callback as Delegate)?.Method.DeclaringType?.DeclaringType == typeof(Control);
+ }
+}