在加载时间长的页面中一般会有Loading,但是如果加载时间中有一部分占用UI线程(一般都要处理把耗时的逻辑放在工作线程,最后赋值的地方在UI线程),Loading就会出现卡顿的效果。
所以使用了后台线程加载Loading,占用UI线程时并不会影响Loading,示例如下:
<Window x:Class="BackGroundLoading.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BackGroundLoading"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Click="ButtonBase_OnClick"></Button>
<Grid x:Name="LoadingControl" Grid.Column="1" Background="Transparent" VerticalAlignment="Center">
<local:DispatcherContainer x:Name="Host" Width="36" Height="36"></local:DispatcherContainer>
</Grid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BackGroundLoading
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private bool loading = false;
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
loading = !loading;
await SetLoadingControlAsync(loading);
}
/// <summary>
/// 设置后台Loading
/// </summary>
/// <param name="visibility"></param>
/// <returns></returns>
private async Task SetLoadingControlAsync(bool visibility)
{
if (Host == null) return;
if (visibility)
{
await Host?.Show<LoadingControl>();
}
else
{
await Host?.Hide();
}
}
}
}
<UserControl x:Class="BackGroundLoading.LoadingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<Grid>
<Viewbox HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid x:Name="LayoutRoot" Background="Transparent"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="Image" RenderTransformOrigin="0.5,0.5"
Source="{StaticResource Image.Operate.SaveFile.Loading}">
<Image.RenderTransform>
<RotateTransform x:Name="SpinnerRotate" Angle="0" />
</Image.RenderTransform>
</Image>
</Grid>
</Viewbox>
</Grid>
</UserControl>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
namespace BackGroundLoading
{
/// <summary>
/// LoadingControl.xaml 的交互逻辑
/// </summary>
public partial class LoadingControl : UserControl
{
public LoadingControl()
{
InitializeComponent();
this.IsVisibleChanged += HandleVisibleChanged;
animationTimer = new DispatcherTimer(DispatcherPriority.ContextIdle, Dispatcher);
animationTimer.Interval = TimeSpan.FromMilliseconds(75);
}
private readonly DispatcherTimer animationTimer;
private void Start()
{
animationTimer.Tick += HandleAnimationTick;
animationTimer.Start();
}
private void Stop()
{
animationTimer.Stop();
animationTimer.Tick -= HandleAnimationTick;
}
private void HandleVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
bool isVisible = (bool)e.NewValue;
if (isVisible)
Start();
else
Stop();
}
private void HandleAnimationTick(object sender, EventArgs e)
{
SpinnerRotate.Angle = (SpinnerRotate.Angle + 36) % 360;
}
}
}
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
namespace BackGroundLoading
{
public sealed class DispatcherContainer : FrameworkElement
{
public DispatcherContainer()
{
_hostVisual = new HostVisual();
}
private readonly HostVisual _hostVisual;
private VisualTargetPresentationSource _targetSource;
#region Child
private bool _isUpdatingChild;
private UIElement _child;
internal UIElement Child => _child;
/// <summary>
/// 设置子项
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dispatcher"></param>
/// <returns></returns>
public async Task SetChildAsync<T>(Dispatcher dispatcher = null)
where T : UIElement, new()
{
await SetChildAsync(() => new T(), dispatcher);
}
public async Task SetChildAsync<T>(Func<T> @new, Dispatcher dispatcher = null)
where T : UIElement
{
dispatcher ??= UIDispatcher.RunNew($"{typeof(T).Name}");
var child = dispatcher.Invoke(@new);
await SetChildAsync(child);
}
public async Task SetChildPropertyAsync (Action action)
{
var visualTarget = _targetSource;
if (visualTarget != null)
{
await visualTarget.Dispatcher.InvokeAsync(action);
}
}
public async Task SetChildAsync(UIElement value)
{
if (_isUpdatingChild)
{
throw new InvalidOperationException("Child property should not be set during Child updating.");
}
_isUpdatingChild = true;
try
{
await SetChildAsync();
}
finally
{
_isUpdatingChild = false;
}
async Task SetChildAsync()
{
var oldChild = _child;
var visualTarget = _targetSource;
if (Equals(oldChild, value))
return;
_targetSource = null;
if (visualTarget != null)
{
RemoveVisualChild(oldChild);
await visualTarget.Dispatcher.InvokeAsync(visualTarget.Dispose);
}
_child = value;
if (value == null)
{
_targetSource = null;
}
else
{
await value.Dispatcher.InvokeAsync(() =>
{
_targetSource = new VisualTargetPresentationSource(_hostVisual)
{
RootVisual = value,
};
});
AddVisualChild(_hostVisual);
}
InvalidateMeasure();
}
}
/// <summary>
/// 显示
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="new"></param>
/// <param name="dispatcher"></param>
/// <returns></returns>
public async Task Show<T>(Dispatcher dispatcher = null)
where T :UIElement, new()
{
if (Child != null)
{
var visualTarget = _targetSource;
if (visualTarget != null)
{
await visualTarget.Dispatcher.InvokeAsync(() => Child.Visibility = Visibility.Visible);
return;
}
}
await SetChildAsync(() => new T(), dispatcher);
}
/// <summary>
/// 隐藏
/// </summary>
/// <returns></returns>
public async Task Hide()
{
if (Child == null )
{
throw new InvalidOperationException("Non existent");
}
var visualTarget = _targetSource;
if (visualTarget != null)
{
await visualTarget.Dispatcher.InvokeAsync(()=>Child.Visibility = Visibility.Collapsed);
}
}
#endregion
#region Tree & Layout
protected override Visual GetVisualChild(int index)
{
if (index != 0)
throw new ArgumentOutOfRangeException(nameof(index));
return _hostVisual;
}
protected override int VisualChildrenCount => _child != null ? 1 : 0;
protected override Size MeasureOverride(Size availableSize)
{
var child = _child;
if (child == null)
return default(Size);
child.Dispatcher.InvokeAsync(
() => child.Measure(availableSize),
DispatcherPriority.Loaded);
return default(Size);
}
protected override Size ArrangeOverride(Size finalSize)
{
var child = _child;
if (child == null)
return finalSize;
child.Dispatcher.InvokeAsync(
() => child.Arrange(new Rect(finalSize)),
DispatcherPriority.Loaded);
return finalSize;
}
#endregion
#region HitTest
protected override HitTestResult HitTestCore(PointHitTestParameters htp)
{
var child = _child;
var element = child?.Dispatcher.Invoke(() =>
{
double offsetX = 0d, offsetY = 0d;
if (child is FrameworkElement fe)
{
offsetX = fe.Margin.Left;
offsetY = fe.Margin.Top;
}
return _child?.InputHitTest(new Point(htp.HitPoint.X - offsetX, htp.HitPoint.Y - offsetY));
}, DispatcherPriority.Normal);
if (element == null)
{
return null;
}
return new PointHitTestResult(this, htp.HitPoint);
}
#endregion
}
}
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace BackGroundLoading
{
public class VisualTargetPresentationSource : PresentationSource, IDisposable
{
public VisualTargetPresentationSource(HostVisual hostVisual)
{
_visualTarget = new VisualTarget(hostVisual);
}
public override Visual RootVisual
{
get => _visualTarget.RootVisual;
set
{
if (IsDisposed)
{
throw new ObjectDisposedException("VisualTarget");
}
var oldRoot = _visualTarget.RootVisual;
_visualTarget.RootVisual = value;
if (oldRoot is FrameworkElement oldRootFe)
{
oldRootFe.SizeChanged -= root_SizeChanged;
}
if (value is FrameworkElement rootFe)
{
rootFe.SizeChanged += root_SizeChanged;
rootFe.DataContext = _dataContext;
if (_propertyName != null)
{
var myBinding = new Binding(_propertyName)
{
Source = _dataContext
};
rootFe.SetBinding(TextBlock.TextProperty, myBinding);
}
}
//告诉PresentationSource根可视化改变。这引发了一系列的事情,比如已加载事件。
RootChanged(oldRoot, value);
//启动布局
if (value is UIElement rootElement)
{
rootElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
rootElement.Arrange(new Rect(rootElement.DesiredSize));
}
}
}
public object DataContext
{
get => _dataContext;
set
{
if (IsDisposed)
{
throw new ObjectDisposedException("VisualTarget");
}
if (_dataContext == value)
{
return;
}
_dataContext = value;
if (_visualTarget.RootVisual is FrameworkElement rootElement)
{
rootElement.DataContext = _dataContext;
}
}
}
public string PropertyName
{
get => _propertyName;
set
{
if (IsDisposed)
{
throw new ObjectDisposedException("VisualTarget");
}
_propertyName = value;
if (_visualTarget.RootVisual is TextBlock rootElement)
{
if (!rootElement.CheckAccess())
{
throw new InvalidOperationException("What?");
}
var myBinding = new Binding(_propertyName)
{
Source = _dataContext
};
rootElement.SetBinding(TextBlock.TextProperty, myBinding);
}
}
}
public event SizeChangedEventHandler SizeChanged;
public override bool IsDisposed => _isDisposed;
protected override CompositionTarget GetCompositionTargetCore()
{
return _visualTarget;
}
private void root_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (IsDisposed)
{
return;
}
SizeChanged?.Invoke(this, e);
}
private readonly VisualTarget _visualTarget;
private object _dataContext;
private string _propertyName;
private bool _isDisposed;
public void Dispose()
{
_visualTarget?.Dispose();
_isDisposed = true;
}
}
}
using System;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Windows.Threading;
namespace BackGroundLoading
{
public static class UIDispatcher
{
/// <summary>
/// 创建一个可以运行 <see cref="Dispatcher"/> 的后台 UI 线程,并返回这个线程的调度器 <see cref="Dispatcher"/>。
/// </summary>
/// <param name="name">线程的名称,如果不指定,将使用 “BackgroundUI”。</param>
/// <returns>一个可以异步等待的 <see cref="Dispatcher"/>。</returns>
public static DispatcherAsyncOperation<Dispatcher> RunNewAsync(string name = null)
{
// 创建一个可等待的异步操作。
var awaiter = DispatcherAsyncOperation<Dispatcher>.Create(out var reportResult);
// 记录原线程关联的 Dispatcher,以便在意外时报告异常。
var originDispatcher = Dispatcher.CurrentDispatcher;
// 创建后台线程。
var thread = new Thread(() =>
{
try
{
// 获取关联此后台线程的 Dispatcher。
var dispatcher = Dispatcher.CurrentDispatcher;
// 设置此线程的 SynchronizationContext,以便此线程上 await 之后能够返回此线程。
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(dispatcher));
// 报告 Dispatcher 已创建完毕,使用 await 异步等待 Dispatcher 创建的地方可以继续执行了。
reportResult(dispatcher, null);
}
catch (Exception ex)
{
// 报告创建过程中发生的异常。
// 不需要担心其内部发生的异常,因为会被异步状态机捕获后重新在原线程上抛出。
reportResult(null, ex);
}
// 此线程的以下代码将脱离异步状态机的控制,需要自己处理异常。
try
{
// 启动 Dispatcher,开始此线程上消息的调度。
Dispatcher.Run();
}
catch (Exception ex)
{
// 如果新的 Dispatcher 线程上出现了未处理的异常,则将其抛到原调用线程上。
originDispatcher.InvokeAsync(() => ExceptionDispatchInfo.Capture(ex).Throw());
}
})
{
Name = name ?? "BackgroundUI",
IsBackground = true,
};
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return awaiter;
}
/// <summary>
/// 创建一个可以运行 <see cref="Dispatcher"/> 的后台 UI 线程,并返回这个线程的调度器 <see cref="Dispatcher"/>。
/// </summary>
/// <param name="name">线程的名称,如果不指定,将使用 “BackgroundUI”。</param>
/// <returns>后台线程创建并启动的 <see cref="Dispatcher"/>。</returns>
public static Dispatcher RunNew(string name = null)
{
var resetEvent = new AutoResetEvent(false);
// 记录原线程关联的 Dispatcher,以便在意外时报告异常。
var originDispatcher = Dispatcher.CurrentDispatcher;
Exception innerException = null;
Dispatcher dispatcher = null;
// 创建后台线程。
var thread = new Thread(() =>
{
try
{
// 获取关联此后台线程的 Dispatcher。
dispatcher = Dispatcher.CurrentDispatcher;
// 设置此线程的 SynchronizationContext,以便此线程上 await 之后能够返回此线程。
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(dispatcher));
// 报告 Dispatcher 已创建完毕,使用 ResetEvent 同步等待 Dispatcher 创建的地方可以继续执行了。
resetEvent.Set();
}
catch (Exception ex)
{
// 报告创建过程中发生的异常。
innerException = ex;
resetEvent.Set();
}
// 此线程的以下代码将脱离异步状态机的控制,需要自己处理异常。
try
{
// 启动 Dispatcher,开始此线程上消息的调度。
Dispatcher.Run();
}
catch (Exception ex)
{
// 如果新的 Dispatcher 线程上出现了未处理的异常,则将其抛到原调用线程上。
originDispatcher.InvokeAsync(() => ExceptionDispatchInfo.Capture(ex).Throw());
}
})
{
Name = name ?? "BackgroundUI",
IsBackground = true,
};
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
resetEvent.WaitOne();
resetEvent.Dispose();
resetEvent = null;
if (innerException != null)
{
ExceptionDispatchInfo.Capture(innerException).Throw();
}
return dispatcher;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace BackGroundLoading
{
/// <summary>
/// 表示可以等待一个主要运行在 UI 线程的异步操作。
/// </summary>
/// <typeparam name="T">异步等待 UI 操作结束后的返回值类型。</typeparam>
public class DispatcherAsyncOperation<T> : DispatcherObject,
IAwaitable<DispatcherAsyncOperation<T>, T>, IAwaiter<T>
{
/// <summary>
/// 创建 <see cref="DispatcherAsyncOperation{T}"/> 的新实例。
/// </summary>
private DispatcherAsyncOperation()
{
}
/// <summary>
/// 获取一个可用于 await 关键字异步等待的异步等待对象。
/// 此方法会被编译器自动调用。
/// </summary>
/// <returns>返回自身,用于异步等待返回值。</returns>
public DispatcherAsyncOperation<T> GetAwaiter()
{
return this;
}
/// <summary>
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常)。
/// 此状态会被编译器自动调用。
/// </summary>
public bool IsCompleted { get; private set; }
/// <summary>
/// 获取此异步等待操作的返回值。
/// 与 <see cref="System.Threading.Tasks.Task{TResult}"/> 不同的是,
/// 如果操作没有完成或发生了异常,此实例会返回 <typeparamref name="T"/> 的默认值,
/// 而不是阻塞线程直至任务完成。
/// </summary>
public T Result { get; private set; }
/// <summary>
/// 获取此异步等待操作的返回值,此方法会被编译器在 await 结束时自动调用以获取返回值。
/// 与 <see cref="System.Threading.Tasks.Task{TResult}"/> 不同的是,
/// 如果操作没有完成,此实例会返回 <typeparamref name="T"/> 的默认值,而不是阻塞线程直至任务完成。
/// 但是,如果异步操作中发生了异常,调用此方法会抛出这个异常。
/// </summary>
/// <returns>
/// 异步操作的返回值。
/// </returns>
public T GetResult()
{
if (_exception != null)
{
ExceptionDispatchInfo.Capture(_exception).Throw();
}
return Result;
}
/// <summary>
/// 使用 Builder 模式配置此异步操作执行完后,后续任务执行采用的优先级。
/// 不配置时,使用的是 <see cref="DispatcherPriority.Normal"/>。
/// </summary>
/// <param name="priority">使用 <see cref="Dispatcher"/> 调度的后续任务的优先级。</param>
/// <returns>实例自身。</returns>
public DispatcherAsyncOperation<T> ConfigurePriority(DispatcherPriority priority)
{
_priority = priority;
return this;
}
/// <summary>
/// 当使用此类型执行异步任务的方法执行完毕后,编译器会自动调用此方法。
/// 也就是说,此方法会在调用方所在的线程执行,用于通知调用方所在线程的代码已经执行完毕,请求执行 await 后续任务。
/// 在此类型中,后续任务是通过 <see cref="Dispatcher.InvokeAsync(Action, DispatcherPriority)"/> 来执行的。
/// </summary>
/// <param name="continuation">
/// 被异步任务状态机包装的后续任务。当执行时,会让状态机继续往下走一步。
/// </param>
public void OnCompleted(Action continuation)
{
if (IsCompleted)
{
// 如果 await 开始时任务已经执行完成,则直接执行 await 后面的代码。
// 注意,即便 _continuation 有值,也无需关心,因为报告结束的时候就会将其执行。
continuation?.Invoke();
}
else
{
// 当使用多个 await 关键字等待此同一个 awaitable 实例时,此 OnCompleted 方法会被多次执行。
// 当任务真正结束后,需要将这些所有的 await 后面的代码都执行。
_continuation += continuation;
}
}
/// <summary>
/// 调用此方法以报告任务结束,并指定返回值和异步任务中的异常。
/// 当使用 <see cref="Create"/> 静态方法创建此类型的实例后,调用方可以通过方法参数中传出的委托来调用此方法。
/// </summary>
/// <param name="result">异步返回值。</param>
/// <param name="exception">异步操作中的异常。</param>
private void ReportResult(T result, Exception exception)
{
Result = result;
_exception = exception;
IsCompleted = true;
// _continuation 可能为 null,说明任务已经执行完毕,但没有任何一处 await 了这个任务。
if (_continuation != null)
{
// 无论此方法执行时所在线程关联的 Dispatcher 是否等于此类型创建时的 Dispatcher;
// 都 Invoke 到创建时的 Dispatcher 上,以便对当前执行上下文造成影响在不同线程执行下都一致(如异常)。
Dispatcher.InvokeAsync(_continuation, _priority);
}
}
/// <summary>
/// 临时保存 await 后后续任务的包装,用于报告任务完成后能够继续执行。
/// </summary>
private Action _continuation;
/// <summary>
/// 临时保存异步任务执行过程中发生的异常。它会在异步等待结束后抛出,以报告异步执行过程中发生的错误。
/// </summary>
private Exception _exception;
/// <summary>
/// 储存恢复 await 后续任务时需要使用的优先级。
/// </summary>
private DispatcherPriority _priority = DispatcherPriority.Normal;
/// <summary>
/// 创建 <see cref="DispatcherAsyncOperation{T}"/> 的新实例,并得到一个可以用于报告操作执行完毕的委托。
/// </summary>
/// <param name="reportResult">一个委托。调用此委托可以报告任务已经执行完毕,并给定返回值和异常信息。</param>
/// <returns>
/// 创建好的 <see cref="DispatcherAsyncOperation{T}"/> 的新实例,将此返回值作为方法的返回值可以让方法支持 await 异步等待。
/// </returns>
public static DispatcherAsyncOperation<T> Create(out Action<T, Exception> reportResult)
{
var asyncOperation = new DispatcherAsyncOperation<T>();
reportResult = asyncOperation.ReportResult;
return asyncOperation;
}
}
}
using System.Runtime.CompilerServices;
namespace BackGroundLoading
{
/// <summary>
/// 表示一个可等待对象,如果一个方法返回此类型的实例,则此方法可以使用 `await` 异步等待。
/// </summary>
/// <typeparam name="TAwaiter">用于给 await 确定返回时机的 IAwaiter 的实例。</typeparam>
public interface IAwaitable<out TAwaiter> where TAwaiter : IAwaiter
{
/// <summary>
/// 获取一个可用于 await 关键字异步等待的异步等待对象。
/// 此方法会被编译器自动调用。
/// </summary>
TAwaiter GetAwaiter();
}
/// <summary>
/// 表示一个包含返回值的可等待对象,如果一个方法返回此类型的实例,则此方法可以使用 `await` 异步等待返回值。
/// </summary>
/// <typeparam name="TAwaiter">用于给 await 确定返回时机的 IAwaiter{<typeparamref name="TResult"/>} 的实例。</typeparam>
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
public interface IAwaitable<out TAwaiter, out TResult> where TAwaiter : IAwaiter<TResult>
{
/// <summary>
/// 获取一个可用于 await 关键字异步等待的异步等待对象。
/// 此方法会被编译器自动调用。
/// </summary>
TAwaiter GetAwaiter();
}
/// <summary>
/// 用于给 await 确定异步返回的时机。
/// </summary>
public interface IAwaiter : INotifyCompletion
{
/// <summary>
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常);此状态会被编译器自动调用。
/// 在实现中,为了达到各种效果,可以灵活应用其值:可以始终为 true,或者始终为 false。
/// </summary>
bool IsCompleted { get; }
/// <summary>
/// 此方法会被编译器在 await 结束时自动调用以获取返回状态(包括异常)。
/// </summary>
void GetResult();
}
/// <summary>
/// 当执行关键代码(此代码中的错误可能给应用程序中的其他状态造成负面影响)时,
/// 用于给 await 确定异步返回的时机。
/// </summary>
public interface ICriticalAwaiter : IAwaiter, ICriticalNotifyCompletion
{
}
/// <summary>
/// 用于给 await 确定异步返回的时机,并获取到返回值。
/// </summary>
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
public interface IAwaiter<out TResult> : INotifyCompletion
{
/// <summary>
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常);此状态会被编译器自动调用。
/// 在实现中,为了达到各种效果,可以灵活应用其值:可以始终为 true,或者始终为 false。
/// </summary>
bool IsCompleted { get; }
/// <summary>
/// 获取此异步等待操作的返回值,此方法会被编译器在 await 结束时自动调用以获取返回值(包括异常)。
/// </summary>
/// <returns>异步操作的返回值。</returns>
TResult GetResult();
}
/// <summary>
/// 当执行关键代码(此代码中的错误可能给应用程序中的其他状态造成负面影响)时,
/// 用于给 await 确定异步返回的时机,并获取到返回值。
/// </summary>
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam>
public interface ICriticalAwaiter<out TResult> : IAwaiter<TResult>, ICriticalNotifyCompletion
{
}
}