任务并行库(Task Parallel Library,简称TPL)是.NET Framework中的一个库,它提供了一种简化的线程处理方式,允许开发者以更直观的方式编写并行代码。然而,在Xamarin等移动应用开发中,使用TPL可能会遇到一些挑战。本文将探讨这些挑战,并介绍一种名为“响应式任务”的解决方案。
当微软宣布推出任务并行库时,C#程序员们为之欢呼。简化的线程处理?库内的错误处理?似乎一切都很完美。然而,现实情况并非如此。
在C#中,一个任务(Task)需要一个根(root)来正确地等待(await)。例如:
public Task YouCanAwaitMe() { }
public async Task IWillAwait()
{
await YouCanAwaitMe().WithoutChangingContext();
}
然而,在Xamarin应用中,并没有有效的根。例如,构造函数、内容更改、绑定上下文更改、事件处理程序、全局消息、重写和属性设置器等都不是有效的根。这会导致不安全的结果。
以下是一个虚假根的示例:
public class FalselyRootedView : ContentView
{
protected override async void OnBindingContextChanged()
{
base.OnBindingContextChanged();
await StartUpViewModel().WithoutChangingContext();
}
public virtual Task StartUpViewModel()
{
return Task.CompletedTask;
}
}
虚假的根会导致任务在不受控制的情况下执行,从而导致竞态条件、变量设置不及时等问题。
以下是在创建ResponsiveAppDemo时的调试输出。该示例在所有禁止的区域调用了任务,包括构造函数。它在其他方面表现良好,至少符合微软的指导。因此,它类似于任何遵循阅读的人可能会产生的代码:
位置 | 任务类型 | 首次/最后一次 |
---|---|---|
Views.Subviews.DashboardView | RunPostConstructionTasks | FIRST |
ViewModels.DashboardViewModel | RunPostConstructionTasks | FIRST |
ViewModels.DashboardViewModel | RunPostBindingTasks | FIRST |
Views.Subviews.DashboardView | RunPostBindingTasks | FIRST |
Views.Subviews.DashboardView | RunPostConstructionTasks | LAST |
ViewModels.DashboardViewModel | RunPostConstructionTasks | LAST |
ViewModels.DashboardViewModel | RunPostBindingTasks | LAST |
Views.Subviews.DashboardView | RunPostBindingTasks | LAST |
一切都立即运行,并且相互叠加。在其他东西堆积并试图依赖某种想象中的状态之前,没有任何东西能够正确形成。这就是导致程序挂起和崩溃的原因。
那么,如何实现每个任务的原子完整性,而不重叠呢?试试这个:
public async void IncorrectlyRaiseATaskWithABlockingCall()
{
await SomeTask.Wait().WithoutChangingContext();
}
讽刺的是,这解决了并发问题,因为它在完成任务后才继续。但这代价巨大:100%的UI线程。用户立即感觉到他们的键盘死了。Wait是一个锈迹斑斑的剃须刀,藏在工具箱底部。
响应式任务库使用线程安全的“等待”策略,以及支持任务的基类,解决了这里提到的所有问题。可以轻松地将代码示例复制到自己的基视图或视图模型中,因此这种方法并不教条。
位置 | 任务类型 | 首次/最后一次 |
---|---|---|
Views.Subviews.DashboardView | RunPostConstructionTasks | FIRST |
Views.Subviews.DashboardView | RunPostConstructionTasks | LAST |
ViewModels.DashboardViewModel | RunPostConstructionTasks | FIRST |
ViewModels.DashboardViewModel | RunPostConstructionTasks | LAST |
ViewModels.DashboardViewModel | RunPostBindingTasks | FIRST |
ViewModels.DashboardViewModel | RunPostBindingTasks | LAST |
Views.Subviews.DashboardView | RunPostBindingTasks | FIRST |
Views.Subviews.DashboardView | RunPostBindingTasks | LAST |