diff --git a/CommunityToolkit.WinUI/CommunityToolkit.WinUI.csproj b/CommunityToolkit.WinUI/CommunityToolkit.WinUI.csproj
index ab0ab1e310c..6cc21e4eea6 100644
--- a/CommunityToolkit.WinUI/CommunityToolkit.WinUI.csproj
+++ b/CommunityToolkit.WinUI/CommunityToolkit.WinUI.csproj
@@ -8,6 +8,7 @@
Windows Community Toolkit - Common (UWP)
This package includes code only helpers such as Color conversion tool, Storage file handling, a Stream helper class, SystemInformation helpers, etc.
Storage;File;Folder;Color;Conversion;Stream;Helpers;Extensions;System;Information
+ true
diff --git a/CommunityToolkit.WinUI/Extensions/DispatcherQueueExtensions.cs b/CommunityToolkit.WinUI/Extensions/DispatcherQueueExtensions.cs
index a2f172a0024..a5e88985ece 100644
--- a/CommunityToolkit.WinUI/Extensions/DispatcherQueueExtensions.cs
+++ b/CommunityToolkit.WinUI/Extensions/DispatcherQueueExtensions.cs
@@ -14,7 +14,7 @@ namespace CommunityToolkit.WinUI
///
/// Helpers for executing code in a .
///
- public static class DispatcherQueueExtensions
+ public static partial class DispatcherQueueExtensions
{
///
/// Invokes a given function on the target and returns a
diff --git a/CommunityToolkit.WinUI/Extensions/DispatcherQueueExtensions{T}.cs b/CommunityToolkit.WinUI/Extensions/DispatcherQueueExtensions{T}.cs
new file mode 100644
index 00000000000..080f1f15c29
--- /dev/null
+++ b/CommunityToolkit.WinUI/Extensions/DispatcherQueueExtensions{T}.cs
@@ -0,0 +1,153 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using CommunityToolkit.WinUI.Interop;
+using Microsoft.UI.Dispatching;
+using WinRT;
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI
+{
+ ///
+ /// A callback that will be executed on the thread.
+ ///
+ /// The type of state to receive as input.
+ /// The input state for the callback.
+ public delegate void DispatcherQueueHandler(T state)
+ where T : class;
+
+ ///
+ /// A callback that will be executed on the thread.
+ ///
+ /// The type of the first state to receive as input.
+ /// The type of the second state to receive as input.
+ /// The first input state for the callback.
+ /// The second input state for the callback.
+ public delegate void DispatcherQueueHandler(T1 state1, T2 state2)
+ where T1 : class
+ where T2 : class;
+
+ ///
+ /// Helpers for executing code in a .
+ ///
+ public static partial class DispatcherQueueExtensions
+ {
+ ///
+ /// Adds a task to the which will be executed on the thread associated with it.
+ ///
+ /// The type of state to capture.
+ /// The target to invoke the code on.
+ /// The input callback to enqueue.
+ /// The input state to capture and pass to the callback.
+ /// Whether or not the task was added to the queue.
+ /// Thrown when the enqueue operation fails.
+ public static unsafe bool TryEnqueue(this DispatcherQueue dispatcherQueue, DispatcherQueueHandler callback, T state)
+ where T : class
+ {
+ return TryEnqueue(dispatcherQueue, null, DispatcherQueueProxyHandler1.Create(callback, state));
+ }
+
+ ///
+ /// Adds a task to the which will be executed on the thread associated with it.
+ ///
+ /// The type of state to capture.
+ /// The target to invoke the code on.
+ /// The desired priority for the callback to schedule.
+ /// The input callback to enqueue.
+ /// The input state to capture and pass to the callback.
+ /// Whether or not the task was added to the queue.
+ /// Thrown when the enqueue operation fails.
+ public static unsafe bool TryEnqueue(this DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority, DispatcherQueueHandler callback, T state)
+ where T : class
+ {
+ return TryEnqueue(dispatcherQueue, priority, DispatcherQueueProxyHandler1.Create(callback, state));
+ }
+
+ ///
+ /// Adds a task to the which will be executed on the thread associated with it.
+ ///
+ /// The type of the first state to capture.
+ /// The type of the second state to capture.
+ /// The target to invoke the code on.
+ /// The input callback to enqueue.
+ /// The first input state to capture and pass to the callback.
+ /// The second input state to capture and pass to the callback.
+ /// Whether or not the task was added to the queue.
+ /// Thrown when the enqueue operation fails.
+ public static unsafe bool TryEnqueue(this DispatcherQueue dispatcherQueue, DispatcherQueueHandler callback, T1 state1, T2 state2)
+ where T1 : class
+ where T2 : class
+ {
+ return TryEnqueue(dispatcherQueue, null, DispatcherQueueProxyHandler2.Create(callback, state1, state2));
+ }
+
+ ///
+ /// Adds a task to the which will be executed on the thread associated with it.
+ ///
+ /// The type of the first state to capture.
+ /// The type of the second state to capture.
+ /// The target to invoke the code on.
+ /// The desired priority for the callback to schedule.
+ /// The input callback to enqueue.
+ /// The first input state to capture and pass to the callback.
+ /// The second input state to capture and pass to the callback.
+ /// Whether or not the task was added to the queue.
+ /// Thrown when the enqueue operation fails.
+ public static unsafe bool TryEnqueue(this DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority, DispatcherQueueHandler callback, T1 state1, T2 state2)
+ where T1 : class
+ where T2 : class
+ {
+ return TryEnqueue(dispatcherQueue, priority, DispatcherQueueProxyHandler2.Create(callback, state1, state2));
+ }
+
+ ///
+ /// Adds a task to the which will be executed on the thread associated with it.
+ ///
+ /// The target to invoke the code on.
+ /// The desired priority for the callback to schedule (if available).
+ /// The input callback to enqueue.
+ /// Whether or not the task was added to the queue.
+ /// Thrown when the enqueue operation fails.
+ private static unsafe bool TryEnqueue(DispatcherQueue dispatcherQueue, DispatcherQueuePriority? priority, THandler* dispatcherQueueHandler)
+ where THandler : unmanaged, IDispatcherQueueHandler
+ {
+ bool success;
+ int hResult;
+
+ try
+ {
+ IDispatcherQueue* dispatcherQueuePtr = (IDispatcherQueue*)((IWinRTObject)dispatcherQueue).NativeObject.ThisPtr;
+
+ if (priority.HasValue)
+ {
+ hResult = dispatcherQueuePtr->TryEnqueueWithPriority(priority.GetValueOrDefault(), dispatcherQueueHandler, (byte*)&success);
+ }
+ else
+ {
+ hResult = dispatcherQueuePtr->TryEnqueue(dispatcherQueueHandler, (byte*)&success);
+ }
+
+ GC.KeepAlive(dispatcherQueue);
+ }
+ finally
+ {
+ // This call doesn not have a corresponding AddRef() invocation that is visible, and
+ // that is because the static constructors for all existing custom handlers already
+ // set the internal reference count to 1. Structuring the code like this makes it
+ // possible to centralize all logic in this method without each caller needing to
+ // explicitly have a try/finally block for the allocated handler that is passed here.
+ dispatcherQueueHandler->Release();
+ }
+
+ if (hResult != 0)
+ {
+ ExceptionHelpers.ThrowExceptionForHR(hResult);
+ }
+
+ return success;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CommunityToolkit.WinUI/Extensions/Interop/DispatcherQueueProxyHandler1.cs b/CommunityToolkit.WinUI/Extensions/Interop/DispatcherQueueProxyHandler1.cs
new file mode 100644
index 00000000000..f7dc8efd514
--- /dev/null
+++ b/CommunityToolkit.WinUI/Extensions/Interop/DispatcherQueueProxyHandler1.cs
@@ -0,0 +1,213 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using WinRT;
+using static CommunityToolkit.WinUI.Interop.Windows;
+
+#nullable enable
+
+#pragma warning disable SA1023
+
+namespace CommunityToolkit.WinUI.Interop
+{
+ ///
+ /// A custom IDispatcherQueueHandler object, that internally stores a captured instance
+ /// and the input captured state. This allows consumers to enqueue a state and a cached stateless delegate without any managed allocations.
+ ///
+ internal unsafe struct DispatcherQueueProxyHandler1 : IDispatcherQueueHandler
+ {
+ ///
+ /// The shared vtable pointer for instances.
+ ///
+ private static readonly void** Vtbl = InitVtbl();
+
+ ///
+ /// Setups the vtable pointer for .
+ ///
+ /// The initialized vtable pointer for .
+ ///
+ /// The vtable itself is allocated with ,
+ /// which allocates memory in the high frequency heap associated with the input runtime type. This will be
+ /// automatically cleaned up when the type is unloaded, so there is no need to ever manually free this memory.
+ ///
+ private static void** InitVtbl()
+ {
+ void** lpVtbl = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(DispatcherQueueProxyHandler1), sizeof(void*) * 4);
+
+ lpVtbl[0] = (delegate* unmanaged)&Impl.QueryInterface;
+ lpVtbl[1] = (delegate* unmanaged)&Impl.AddRef;
+ lpVtbl[2] = (delegate* unmanaged)&Impl.Release;
+ lpVtbl[3] = (delegate* unmanaged)&Impl.Invoke;
+
+ return lpVtbl;
+ }
+
+ ///
+ /// The vtable pointer for the current instance.
+ ///
+ private void** lpVtbl;
+
+ ///
+ /// The to the captured (for some unknown TState type).
+ ///
+ private GCHandle callbackHandle;
+
+ ///
+ /// The to the captured state (with an unknown TState type).
+ ///
+ private GCHandle stateHandle;
+
+ ///
+ /// The generic stub to invoke the current callback with the right generic context.
+ ///
+ private delegate* stub;
+
+ ///
+ /// The current reference count for the object (from IUnknown).
+ ///
+ private volatile uint referenceCount;
+
+ ///
+ /// Creates a new instance for the input callback and state.
+ ///
+ /// The type of state currently being used.
+ /// The input callback to enqueue.
+ /// The input state to capture and pass to the callback.
+ /// A pointer to the newly initialized instance.
+ public static DispatcherQueueProxyHandler1* Create(DispatcherQueueHandler handler, T state)
+ where T : class
+ {
+ DispatcherQueueProxyHandler1* @this = (DispatcherQueueProxyHandler1*)Marshal.AllocHGlobal(sizeof(DispatcherQueueProxyHandler1));
+
+ @this->lpVtbl = Vtbl;
+ @this->callbackHandle = GCHandle.Alloc(handler);
+ @this->stateHandle = GCHandle.Alloc(state);
+ @this->stub = &Impl.Invoke;
+ @this->referenceCount = 1;
+
+ return @this;
+ }
+
+ ///
+ /// Devirtualized API for IUnknown.AddRef().
+ ///
+ /// The updated reference count for the current instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public uint AddRef()
+ {
+ return Interlocked.Increment(ref referenceCount);
+ }
+
+ ///
+ /// Devirtualized API for IUnknown.Release().
+ ///
+ /// The updated reference count for the current instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public uint Release()
+ {
+ uint referenceCount = Interlocked.Decrement(ref this.referenceCount);
+
+ if (referenceCount == 0)
+ {
+ callbackHandle.Free();
+ stateHandle.Free();
+
+ Marshal.FreeHGlobal((IntPtr)Unsafe.AsPointer(ref this));
+ }
+
+ return referenceCount;
+ }
+
+ ///
+ /// A private type with the implementation of the unmanaged methods for .
+ /// These methods will be set into the shared vtable and invoked by WinRT from the object passed to it as an interface.
+ ///
+ private static class Impl
+ {
+ ///
+ /// Implements IUnknown.QueryInterface(REFIID, void**).
+ ///
+ [UnmanagedCallersOnly]
+ public static int QueryInterface(DispatcherQueueProxyHandler1* @this, Guid* riid, void** ppvObject)
+ {
+ if (riid->Equals(GuidOfIUnknown) ||
+ riid->Equals(GuidOfIAgileObject) ||
+ riid->Equals(GuidOfIDispatcherQueueHandler))
+ {
+ @this->AddRef();
+
+ *ppvObject = @this;
+
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+
+ ///
+ /// Implements IUnknown.AddRef().
+ ///
+ [UnmanagedCallersOnly]
+ public static uint AddRef(DispatcherQueueProxyHandler1* @this)
+ {
+ return Interlocked.Increment(ref @this->referenceCount);
+ }
+
+ ///
+ /// Implements IUnknown.Release().
+ ///
+ [UnmanagedCallersOnly]
+ public static uint Release(DispatcherQueueProxyHandler1* @this)
+ {
+ uint referenceCount = Interlocked.Decrement(ref @this->referenceCount);
+
+ if (referenceCount == 0)
+ {
+ @this->callbackHandle.Free();
+ @this->stateHandle.Free();
+
+ Marshal.FreeHGlobal((IntPtr)@this);
+ }
+
+ return referenceCount;
+ }
+
+ ///
+ /// Implements IDispatcherQueueHandler.Invoke().
+ ///
+ [UnmanagedCallersOnly]
+ public static int Invoke(DispatcherQueueProxyHandler1* @this)
+ {
+ return @this->stub(@this);
+ }
+
+ ///
+ /// Implements IDispatcherQueueHandler.Invoke() from within a generic context.
+ ///
+ public static int Invoke(DispatcherQueueProxyHandler1* @this)
+ where T : class
+ {
+ object callback = @this->callbackHandle.Target!;
+ object state = @this->stateHandle.Target!;
+
+ try
+ {
+ Unsafe.As>(callback)(Unsafe.As(state));
+ }
+ catch (Exception e)
+ {
+ ExceptionHelpers.SetErrorInfo(e);
+
+ return ExceptionHelpers.GetHRForException(e);
+ }
+
+ return S_OK;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CommunityToolkit.WinUI/Extensions/Interop/DispatcherQueueProxyHandler2.cs b/CommunityToolkit.WinUI/Extensions/Interop/DispatcherQueueProxyHandler2.cs
new file mode 100644
index 00000000000..cfb4161313d
--- /dev/null
+++ b/CommunityToolkit.WinUI/Extensions/Interop/DispatcherQueueProxyHandler2.cs
@@ -0,0 +1,228 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using WinRT;
+using static CommunityToolkit.WinUI.Interop.Windows;
+
+#nullable enable
+
+#pragma warning disable SA1023
+
+namespace CommunityToolkit.WinUI.Interop
+{
+ ///
+ /// A custom IDispatcherQueueHandler object, that internally stores a captured instance
+ /// and the input captured state. This allows consumers to enqueue a state and a cached stateless delegate without any managed allocations.
+ ///
+ internal unsafe struct DispatcherQueueProxyHandler2 : IDispatcherQueueHandler
+ {
+ ///
+ /// The shared vtable pointer for instances.
+ ///
+ private static readonly void** Vtbl = InitVtbl();
+
+ ///
+ /// Setups the vtable pointer for .
+ ///
+ /// The initialized vtable pointer for .
+ ///
+ /// The vtable itself is allocated with ,
+ /// which allocates memory in the high frequency heap associated with the input runtime type. This will be
+ /// automatically cleaned up when the type is unloaded, so there is no need to ever manually free this memory.
+ ///
+ private static void** InitVtbl()
+ {
+ void** lpVtbl = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(DispatcherQueueProxyHandler2), sizeof(void*) * 4);
+
+ lpVtbl[0] = (delegate* unmanaged)&Impl.QueryInterface;
+ lpVtbl[1] = (delegate* unmanaged)&Impl.AddRef;
+ lpVtbl[2] = (delegate* unmanaged)&Impl.Release;
+ lpVtbl[3] = (delegate* unmanaged)&Impl.Invoke;
+
+ return lpVtbl;
+ }
+
+ ///
+ /// The vtable pointer for the current instance.
+ ///
+ private void** lpVtbl;
+
+ ///
+ /// The to the captured (for some unknown TState type).
+ ///
+ private GCHandle callbackHandle;
+
+ ///
+ /// The to the first captured state (with an unknown T1 type).
+ ///
+ private GCHandle state1Handle;
+
+ ///
+ /// The to the second captured state (with an unknown T2 type).
+ ///
+ private GCHandle state2Handle;
+
+ ///
+ /// The generic stub to invoke the current callback with the right generic context.
+ ///
+ private delegate* stub;
+
+ ///
+ /// The current reference count for the object (from IUnknown).
+ ///
+ private volatile uint referenceCount;
+
+ ///
+ /// Creates a new instance for the input callback and state.
+ ///
+ /// The type of the first state to capture.
+ /// The type of the second state to capture.
+ /// The input callback to enqueue.
+ /// The first input state to capture and pass to the callback.
+ /// The second input state to capture and pass to the callback.
+ /// A pointer to the newly initialized instance.
+ public static DispatcherQueueProxyHandler2* Create(DispatcherQueueHandler handler, T1 state1, T2 state2)
+ where T1 : class
+ where T2 : class
+ {
+ DispatcherQueueProxyHandler2* @this = (DispatcherQueueProxyHandler2*)Marshal.AllocHGlobal(sizeof(DispatcherQueueProxyHandler2));
+
+ @this->lpVtbl = Vtbl;
+ @this->callbackHandle = GCHandle.Alloc(handler);
+ @this->state1Handle = GCHandle.Alloc(state1);
+ @this->state2Handle = GCHandle.Alloc(state2);
+ @this->stub = &Impl.Invoke;
+ @this->referenceCount = 1;
+
+ return @this;
+ }
+
+ ///
+ /// Devirtualized API for IUnknown.AddRef().
+ ///
+ /// The updated reference count for the current instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public uint AddRef()
+ {
+ return Interlocked.Increment(ref referenceCount);
+ }
+
+ ///
+ /// Devirtualized API for IUnknown.Release().
+ ///
+ /// The updated reference count for the current instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public uint Release()
+ {
+ uint referenceCount = Interlocked.Decrement(ref this.referenceCount);
+
+ if (referenceCount == 0)
+ {
+ callbackHandle.Free();
+ state1Handle.Free();
+ state2Handle.Free();
+
+ Marshal.FreeHGlobal((IntPtr)Unsafe.AsPointer(ref this));
+ }
+
+ return referenceCount;
+ }
+
+ ///
+ /// A private type with the implementation of the unmanaged methods for .
+ /// These methods will be set into the shared vtable and invoked by WinRT from the object passed to it as an interface.
+ ///
+ private static class Impl
+ {
+ ///
+ /// Implements IUnknown.QueryInterface(REFIID, void**).
+ ///
+ [UnmanagedCallersOnly]
+ public static int QueryInterface(DispatcherQueueProxyHandler2* @this, Guid* riid, void** ppvObject)
+ {
+ if (riid->Equals(GuidOfIUnknown) ||
+ riid->Equals(GuidOfIAgileObject) ||
+ riid->Equals(GuidOfIDispatcherQueueHandler))
+ {
+ @this->AddRef();
+
+ *ppvObject = @this;
+
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+
+ ///
+ /// Implements IUnknown.AddRef().
+ ///
+ [UnmanagedCallersOnly]
+ public static uint AddRef(DispatcherQueueProxyHandler2* @this)
+ {
+ return Interlocked.Increment(ref @this->referenceCount);
+ }
+
+ ///
+ /// Implements IUnknown.Release().
+ ///
+ [UnmanagedCallersOnly]
+ public static uint Release(DispatcherQueueProxyHandler2* @this)
+ {
+ uint referenceCount = Interlocked.Decrement(ref @this->referenceCount);
+
+ if (referenceCount == 0)
+ {
+ @this->callbackHandle.Free();
+ @this->state1Handle.Free();
+ @this->state2Handle.Free();
+
+ Marshal.FreeHGlobal((IntPtr)@this);
+ }
+
+ return referenceCount;
+ }
+
+ ///
+ /// Implements IDispatcherQueueHandler.Invoke().
+ ///
+ [UnmanagedCallersOnly]
+ public static int Invoke(DispatcherQueueProxyHandler2* @this)
+ {
+ return @this->stub(@this);
+ }
+
+ ///
+ /// Implements IDispatcherQueueHandler.Invoke() from within a generic context.
+ ///
+ public static int Invoke(DispatcherQueueProxyHandler2* @this)
+ where T1 : class
+ where T2 : class
+ {
+ object callback = @this->callbackHandle.Target!;
+ object state1 = @this->state1Handle.Target!;
+ object state2 = @this->state2Handle.Target!;
+
+ try
+ {
+ Unsafe.As>(callback)(
+ Unsafe.As(state1),
+ Unsafe.As(state2));
+ }
+ catch (Exception e)
+ {
+ ExceptionHelpers.SetErrorInfo(e);
+
+ return ExceptionHelpers.GetHRForException(e);
+ }
+
+ return S_OK;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CommunityToolkit.WinUI/Extensions/Interop/IDispatcherQueue.cs b/CommunityToolkit.WinUI/Extensions/Interop/IDispatcherQueue.cs
new file mode 100644
index 00000000000..c9950307764
--- /dev/null
+++ b/CommunityToolkit.WinUI/Extensions/Interop/IDispatcherQueue.cs
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using Microsoft.UI.Dispatching;
+
+#pragma warning disable CS0649, SA1023
+
+namespace CommunityToolkit.WinUI.Interop
+{
+ ///
+ /// A struct mapping the native WinRT IDispatcherQueue interface.
+ ///
+ internal unsafe struct IDispatcherQueue
+ {
+ ///
+ /// The vtable pointer for the current instance.
+ ///
+ private readonly void** lpVtbl;
+
+ ///
+ /// Native API for .
+ ///
+ /// A pointer to an object.
+ /// The result of the operation (the WinRT retval).
+ /// The HRESULT for the operation.
+ ///
+ /// The parameter is assumed to be a pointer to an object, but it
+ /// is just typed as a to avoid unnecessary generic instantiations for this method (as it just needs to pass a
+ /// pointer to the native side anyway). The is an actual C# interface and not a C++ one as it
+ /// is only used internally to constrain type parameters and allow calls to to be inlined.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int TryEnqueue(void* callback, byte* result)
+ {
+ return ((delegate* unmanaged)lpVtbl[7])((IDispatcherQueue*)Unsafe.AsPointer(ref this), callback, result);
+ }
+
+ ///
+ /// Native API for .
+ ///
+ /// The priority for the input callback.
+ /// A pointer to an object.
+ /// The result of the operation (the WinRT retval).
+ /// The HRESULT for the operation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int TryEnqueueWithPriority(DispatcherQueuePriority priority, void* callback, byte* result)
+ {
+ return ((delegate* unmanaged)lpVtbl[8])((IDispatcherQueue*)Unsafe.AsPointer(ref this), priority, callback, result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CommunityToolkit.WinUI/Extensions/Interop/IDispatcherQueueHandler.cs b/CommunityToolkit.WinUI/Extensions/Interop/IDispatcherQueueHandler.cs
new file mode 100644
index 00000000000..af01bb29c63
--- /dev/null
+++ b/CommunityToolkit.WinUI/Extensions/Interop/IDispatcherQueueHandler.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Interop
+{
+ ///
+ /// An interface mapping the native WinRT IDispatcherQueueHandler interface.
+ ///
+ internal interface IDispatcherQueueHandler
+ {
+ ///
+ /// Implements IUnknown.Release().
+ ///
+ /// The updated reference count for the current instance.
+ uint Release();
+ }
+}
\ No newline at end of file
diff --git a/CommunityToolkit.WinUI/Extensions/Interop/Windows.cs b/CommunityToolkit.WinUI/Extensions/Interop/Windows.cs
new file mode 100644
index 00000000000..95df0bf79e9
--- /dev/null
+++ b/CommunityToolkit.WinUI/Extensions/Interop/Windows.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace CommunityToolkit.WinUI.Interop
+{
+ ///
+ /// A helper class with some shared constants from the Windows headers.
+ ///
+ internal static class Windows
+ {
+ ///
+ /// The HRESULT for a successful operation.
+ ///
+ public const int S_OK = 0;
+
+ ///
+ /// The HRESULT for an invalid cast from IUnknown.QueryInterface.
+ ///
+ public const int E_NOINTERFACE = unchecked((int)0x80004002);
+
+ ///
+ /// The GUID for the IUnknown COM interface.
+ ///
+ public static readonly Guid GuidOfIUnknown = new(0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
+
+ ///
+ /// The GUID for the IAgileObject WinRT interface.
+ ///
+ public static readonly Guid GuidOfIAgileObject = new(0x94EA2B94, 0xE9CC, 0x49E0, 0xC0, 0xFF, 0xEE, 0x64, 0xCA, 0x8F, 0x5B, 0x90);
+
+ ///
+ /// The GUID for the IDispatcherQueueHandler WinRT interface.
+ ///
+ public static readonly Guid GuidOfIDispatcherQueueHandler = new(0x2E0872A9, 0x4E29, 0x5F14, 0xB6, 0x88, 0xFB, 0x96, 0xD5, 0xF9, 0xD5, 0xF8);
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/UnitTests.UWP/Extensions/Test_DispatcherQueueExtensions.cs b/UnitTests/UnitTests.UWP/Extensions/Test_DispatcherQueueExtensions.cs
index 5313f5bdcd8..6d1663cf68e 100644
--- a/UnitTests/UnitTests.UWP/Extensions/Test_DispatcherQueueExtensions.cs
+++ b/UnitTests/UnitTests.UWP/Extensions/Test_DispatcherQueueExtensions.cs
@@ -287,5 +287,167 @@ public void Test_DispatcherQueueHelper_FuncOfTaskOfT_Exception()
Assert.IsNotNull(task.Exception);
Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException));
}
+
+ [TestCategory("DispatcherQueueExtensions")]
+ [TestMethod]
+ public async Task Test_DispatcherQueueExtensions_Stateful_TryEnqueueT1_Ok_NonUIThread()
+ {
+ TaskCompletionSource taskSource = new();
+
+ _ = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ CoreDispatcherPriority.Normal,
+ async () =>
+ {
+ try
+ {
+ var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
+
+ bool success = dispatcherQueue.TryEnqueue(x =>
+ {
+ Assert.AreEqual(x, nameof(Test_DispatcherQueueExtensions_Stateful_TryEnqueueT1_Ok_NonUIThread));
+
+ var textBlock = new TextBlock { Text = x };
+
+ Assert.AreEqual(textBlock.Text, x);
+
+ taskSource.SetResult();
+ }, nameof(Test_DispatcherQueueExtensions_Stateful_TryEnqueueT1_Ok_NonUIThread));
+
+ if (!success)
+ {
+ taskSource.SetException(new Exception("Failed to enqueue task"));
+ }
+ }
+ catch (Exception e)
+ {
+ taskSource.SetException(e);
+ }
+ });
+
+ return taskSource.Task;
+ }
+
+ [TestCategory("DispatcherQueueExtensions")]
+ [TestMethod]
+ public async Task Test_DispatcherQueueExtensions_Stateful_TryEnqueueT1T2_Ok_NonUIThread()
+ {
+ TaskCompletionSource taskSource = new();
+
+ _ = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ CoreDispatcherPriority.Normal,
+ async () =>
+ {
+ try
+ {
+ var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
+
+ bool success = dispatcherQueue.TryEnqueue((x, tcs) =>
+ {
+ Assert.AreEqual(x, nameof(Test_DispatcherQueueExtensions_Stateful_TryEnqueueT1T2_Ok_NonUIThread));
+ Assert.AreSame(tcs, taskSource);
+
+ var textBlock = new TextBlock { Text = x };
+
+ Assert.AreEqual(textBlock.Text, x);
+
+ tcs.SetResult();
+ }, nameof(Test_DispatcherQueueExtensions_Stateful_TryEnqueueT1T2_Ok_NonUIThread), taskSource);
+
+ if (!success)
+ {
+ taskSource.SetException(new Exception("Failed to enqueue task"));
+ }
+ }
+ catch (Exception e)
+ {
+ taskSource.SetException(e);
+ }
+ });
+
+ return taskSource.Task;
+ }
+
+ [TestCategory("DispatcherQueueExtensions")]
+ [TestMethod]
+ public async Task Test_DispatcherQueueExtensions_Stateful_WithPriority_TryEnqueueT1_Ok_NonUIThread()
+ {
+ TaskCompletionSource taskSource = new();
+
+ _ = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ CoreDispatcherPriority.Normal,
+ async () =>
+ {
+ try
+ {
+ var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
+
+ bool success = dispatcherQueue.TryEnqueue(
+ DispatcherQueuePriority.Normal,
+ x =>
+ {
+ Assert.AreEqual(x, nameof(Test_DispatcherQueueExtensions_Stateful_WithPriority_TryEnqueueT1_Ok_NonUIThread));
+
+ var textBlock = new TextBlock { Text = x };
+
+ Assert.AreEqual(textBlock.Text, x);
+
+ taskSource.SetResult();
+ }, nameof(Test_DispatcherQueueExtensions_Stateful_WithPriority_TryEnqueueT1_Ok_NonUIThread));
+
+ if (!success)
+ {
+ taskSource.SetException(new Exception("Failed to enqueue task"));
+ }
+ }
+ catch (Exception e)
+ {
+ taskSource.SetException(e);
+ }
+ });
+
+ return taskSource.Task;
+ }
+
+ [TestCategory("DispatcherQueueExtensions")]
+ [TestMethod]
+ public async Task Test_DispatcherQueueExtensions_Stateful_WithPriority_TryEnqueueT1T2_Ok_NonUIThread()
+ {
+ TaskCompletionSource taskSource = new();
+
+ _ = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+ CoreDispatcherPriority.Normal,
+ async () =>
+ {
+ try
+ {
+ var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
+
+ bool success = dispatcherQueue.TryEnqueue(
+ DispatcherQueuePriority.Normal,
+ (x, tcs) =>
+ {
+ Assert.AreEqual(x, nameof(Test_DispatcherQueueExtensions_Stateful_WithPriority_TryEnqueueT1T2_Ok_NonUIThread));
+ Assert.AreSame(tcs, taskSource);
+
+ var textBlock = new TextBlock { Text = x };
+
+ Assert.AreEqual(textBlock.Text, x);
+
+ tcs.SetResult();
+ }, nameof(Test_DispatcherQueueExtensions_Stateful_WithPriority_TryEnqueueT1T2_Ok_NonUIThread), taskSource);
+
+ if (!success)
+ {
+ taskSource.SetException(new Exception("Failed to enqueue task"));
+ }
+ }
+ catch (Exception e)
+ {
+ taskSource.SetException(e);
+ }
+ });
+
+ return taskSource.Task;
+ }
}
}
\ No newline at end of file