File: WindowsFormsDispatcher.cs
Web Access
Project: src\src\BlazorWebView\src\WindowsForms\Microsoft.AspNetCore.Components.WebView.WindowsForms.csproj (Microsoft.AspNetCore.Components.WebView.WindowsForms)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
using System;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace Microsoft.AspNetCore.Components.WebView.WindowsForms
{
	/// <summary>
	/// Dispatcher implementation for Windows Forms that invokes methods on the UI thread. The <see cref="Dispatcher"/>
	/// class uses the async <see cref="Task"/> pattern so everything must be mapped from the <see cref="IAsyncResult"/>
	/// pattern using techniques listed in https://docs.microsoft.com/dotnet/standard/asynchronous-programming-patterns/interop-with-other-asynchronous-patterns-and-types.
	/// </summary>
	internal sealed class WindowsFormsDispatcher : Dispatcher
	{
		private static Action<Exception> RethrowException = exception =>
			ExceptionDispatchInfo.Capture(exception).Throw();
		private readonly Control _dispatchThreadControl;
 
		/// <summary>
		/// Creates a new instance of <see cref="WindowsFormsDispatcher"/>.
		/// </summary>
		/// <param name="dispatchThreadControl">A control that was created on the thread from which UI dispatches must
		/// occur. This can typically be any control because all controls must have been created on the UI thread to
		/// begin with.</param>
		public WindowsFormsDispatcher(Control dispatchThreadControl)
		{
			if (dispatchThreadControl is null)
			{
				throw new ArgumentNullException(nameof(dispatchThreadControl));
			}
 
			_dispatchThreadControl = dispatchThreadControl;
		}
 
		public override bool CheckAccess()
			=> !_dispatchThreadControl.InvokeRequired;
 
		public override async Task InvokeAsync(Action workItem)
		{
			try
			{
				if (CheckAccess())
				{
					workItem();
				}
				else
				{
					var asyncResult = _dispatchThreadControl.BeginInvoke(workItem);
					await Task.Factory.FromAsync(asyncResult, _dispatchThreadControl.EndInvoke);
				}
			}
			catch (Exception ex)
			{
				// TODO: Determine whether this is the right kind of rethrowing pattern
				// You do have to do something like this otherwise unhandled exceptions
				// throw from inside Dispatcher.InvokeAsync are simply lost.
				_ = _dispatchThreadControl.BeginInvoke(RethrowException, ex);
				throw;
			}
		}
 
		public override async Task InvokeAsync(Func<Task> workItem)
		{
			try
			{
				if (CheckAccess())
				{
					await workItem();
				}
				else
				{
					// See https://github.com/dotnet/winforms/issues/4631 for discussion. `Control.BeginInvoke` in WinForms
					// does not wait for Tasks returned by the delegate. We will have to simulate this using a TCS and wait for
					// both execution of `workItem` and the dispatcher to complete its internal operation.
					// additional APIs are exposed by WinForms.
 
					var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
					// BeginInvoke specifically expects an `Action` so avoid using var here.
					Action action = async () =>
					{
						try
						{
							await workItem();
							tcs.TrySetResult();
						}
						catch (Exception ex)
						{
							tcs.TrySetException(ex);
						}
					};
 
					var asyncResult = _dispatchThreadControl.BeginInvoke(action, workItem, tcs);
					await Task.WhenAll(tcs.Task, Task.Factory.FromAsync(asyncResult, _dispatchThreadControl.EndInvoke));
				}
			}
			catch (Exception ex)
			{
				// TODO: Determine whether this is the right kind of rethrowing pattern
				// You do have to do something like this otherwise unhandled exceptions
				// throw from inside Dispatcher.InvokeAsync are simply lost.
				_ = _dispatchThreadControl.BeginInvoke(RethrowException, ex);
				throw;
			}
		}
 
		public override async Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
		{
			try
			{
				if (CheckAccess())
				{
					return workItem();
				}
				else
				{
					var asyncResult = _dispatchThreadControl.BeginInvoke(workItem);
					return await Task<TResult>.Factory.FromAsync(asyncResult, result => (TResult)_dispatchThreadControl.EndInvoke(result)!);
				}
			}
			catch (Exception ex)
			{
				// TODO: Determine whether this is the right kind of rethrowing pattern
				// You do have to do something like this otherwise unhandled exceptions
				// throw from inside Dispatcher.InvokeAsync are simply lost.
				_ = _dispatchThreadControl.BeginInvoke(RethrowException, ex);
				throw;
			}
		}
 
		public override async Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
		{
			try
			{
				if (CheckAccess())
				{
					return await workItem();
				}
				else
				{
					// See https://github.com/dotnet/winforms/issues/4631 for discussion. `Control.BeginInvoke` in WinForms
					// does not wait for Tasks returned by the delegate. We will have to simulate this using a TCS and wait for
					// both execution of `workItem` and the dispatcher to complete its internal operation.
					// additional APIs are exposed by WinForms.
 
					var tcs = new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
					// BeginInvoke specifically expects an `Action` so avoid using var here.
					Action action = async () =>
					{
						try
						{
							var result = await workItem();
							tcs.TrySetResult(result);
						}
						catch (Exception ex)
						{
							tcs.TrySetException(ex);
						}
					};
 
					var asyncResult = _dispatchThreadControl.BeginInvoke(action, workItem, tcs);
					await Task.WhenAll(tcs.Task, Task.Factory.FromAsync(asyncResult, _dispatchThreadControl.EndInvoke));
					return await tcs.Task;
				}
			}
			catch (Exception ex)
			{
				// TODO: Determine whether this is the right kind of rethrowing pattern
				// You do have to do something like this otherwise unhandled exceptions
				// throw from inside Dispatcher.InvokeAsync are simply lost.
				_ = _dispatchThreadControl.BeginInvoke(RethrowException, ex);
				throw;
			}
		}
	}
}