假设
Executor()
正在 UI 线程上运行。
void Executor()
{
BadAsync();
Thread.Sleep(1000); // E1
Thread.Sleep(1000); // E2
}
async void BadAsync()
{
await Task.Delay(1000); // B1
Thread.Sleep(1000); // B2
}
Executor()
不等待 BadAsync()
,则 B1
和 E1
在第一秒内同时运行。B1
捕获 UI 线程时,B2
也在 UI 线程上运行。x
和 y
,分别用于 B2
和 E2
。B2
会占用哪个时段吗?
使用 MAUI 的最小示例。
<VerticalStackLayout>
<Label x:Name="start" Text="Start"/>
<Label x:Name="stop" Text="Stop"/>
</VerticalStackLayout>
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
start.Text = DateTime.UtcNow.ToLongTimeString();
}
protected override async void OnAppearing() // analogous to BadAsync()
{
var d = 979; // in my computer 979 is the average of critical values
await Task.Delay(d);
Thread.Sleep(5000 - d);
stop.Text = DateTime.UtcNow.ToLongTimeString();
}
}
// Page.cs
[EditorBrowsable(EditorBrowsableState.Never)]
public void SendAppearing()// analogous to Executor()
{
// others ...
OnAppearing();
Appearing?.Invoke(this, EventArgs.Empty);
// others ...
}
当
d
设置为远低于979的值时,输出首先以空白窗口开始,然后在几毫秒后切换到以下内容。
在这种情况下,
B2
在SendAppearing()
中执行(或在SendAppearing()
返回之前)。
但是当
d
设置为远高于979的值时,输出以开始
先然后
几毫秒后。
在这种情况下,
B2
在SendAppearing()
之外执行(或在SendAppearing()
返回之后)。
public partial class MainPage : ContentPage
{
public MainPage(MainPageViewModel model)
{
InitializeComponent();
BindingContext = model;
}
protected override async void OnAppearing() // analogous to BadAsync()
{
if(BindingContext is MainPageViewModel model)
{
await model.LoadDataAsync();
UpdateUISync();
}
}
}
B2 将在 E2 之后运行。
Task.Delegate 只是 System.Threading.Timer 的包装。所以你的例子将被转换成这样的:
void Executor()
{
BadAsync();
Thread.Sleep(1000); // E1
Thread.Sleep(1000); // E2
}
SynchronizationContext context;
System.Threading.Timer timer;
void BadAsync()
{
context = SynchronizationContext.Current;
timer = new System.Threading.Timer(OnTimerElapsed, null, 1000, Timeout.Infinite );
}
void OnTimerElapsed(object? _){
timer.Dispose();
if(context != null){
context.Post(B2);
}
else{
B2();
}
}
void B2(){
Thread.Sleep(1000); // B2
}
假设我们有可用的线程,因此
OnTimerElapsed
将在计时器启动后 1 秒运行。如果在 UI 线程上调用 Executor
,则当 UI 线程在 E2 中休眠时,将调用 context.Post(B2)
。
context.Post
本质上只是将消息添加到 UI 线程从中读取的线程安全队列(消息队列)。但是,如果 UI 线程很忙(通过睡眠),它无法处理 any 消息,因此它首先必须完成正在执行的操作,返回到消息循环,处理任何其他消息,然后最终在其运行时运行 B2
消息已处理。
这是您应该避免在 UI 线程上运行任何耗时操作的原因之一。如果消息未被处理,应用程序将显示为“冻结”。所以不要阻塞 UI 线程。
您通常希望通过确保所有异步方法返回任务并且等待所有任务来明确运行顺序。这也有助于确保处理故障。主要的异常是事件处理程序,您应该确保处理任何异常。