为什么在WPF UI线程中执行异步回调
本文关键字:执行 异步 回调 线程 WPF UI 为什么 | 更新日期: 2023-09-27 18:10:44
关于这段代码:
static async Task<string> testc()
{
Console.WriteLine("helo async " + Thread.CurrentThread.ManagedThreadId);
await Task.Run(() => {
Thread.Sleep(1000);
Console.WriteLine("task " + Thread.CurrentThread.ManagedThreadId);
});
Console.WriteLine("callback "+Thread.CurrentThread.ManagedThreadId);
return "bob";
}
static void Main(string[] args)
{
Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId);
testc();
Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.ReadLine();
}
我得到以下输出:
helo sync 10
helo async 10
over10
task 11
callback **11**
这是可以的:等待之后的代码段在与任务本身相同的线程中执行。
现在,如果我在WPF应用程序中这样做:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId);
testc();
Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.ReadLine();
}
它生成以下输出:
helo sync 8
helo async 8
over8
task 9
callback **8**
在这里我们可以看到在UI线程中执行等待之后的代码。好吧,这很好,因为它可以操作可观察的集合等……但我想知道"为什么?"我怎么能这样做?"这与TaskScheduler的某些行为有关吗?这是中的硬编码吗。NET框架?
谢谢你提出的任何想法。
原因是Task。当从UI线程启动任务时,Run将捕获SynchronizationContext
(如果存在(,就像它在WPF应用程序中一样。然后,Task将使用SynchronizationContext
将回调序列化到UI线程。但是,如果没有像Console应用程序中那样的上下文可用,则回调将发生在另一个线程上。
Stephen Toub在一篇博客文章中描述了这一点。
顺便说一句,使用时要小心,千万不要使用Thread。在任务中睡眠。它可能会导致奇怪的行为,因为任务可能没有绑定到一个线程。使用任务。而是延迟。
但我想知道"为什么?">
你自己已经回答了:
好吧,这很好,因为它可以操作可观察的集合等…
异步的全部目的是使异步更容易使用,这样您就可以编写实际上是异步的"看起来同步"的代码。这通常包括希望为整个异步方法保留在一个上下文(例如,UI线程(中——当您需要等待某件事时,只需"暂停"该方法(而不阻塞UI线程(。
"我怎么能做同样的事?">
你在这里的意思还不清楚。基本上,Task
的awaitable模式的实现使用TaskScheduler.FromCurrentSynchronizationContext()
来确定在哪个调度程序上发布回调——除非您已经调用ConfigureAwait(false)
来明确选择退出这种行为。这就是它的管理方式……你是否能"做同样的事情"取决于你想做什么
有关awaitable模式的更多详细信息,请参阅async/await FAQ中的"什么是awaitables"问题。
您可能会发现我的async
/await
介绍很有帮助。其他答案几乎正确。
当await
是一个尚未完成的Task
时,默认情况下会捕获一个"上下文",用于在Task
完成时恢复该方法。这个"上下文"是SynchronizationContext.Current
,除非它为null,在这种情况下它是TaskScheduler.Current
。
注意工作所需的条件:
- "当您
await
…"-如果手动安排延续,例如使用Task.ContinueWith
,则不会执行上下文捕获。你必须用(SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext())
这样的东西自己做 - "…
await
aTask
…"-此行为是Task
类型的await
行为的一部分。其他类型可能会也可能不会进行类似的捕获 - "…尚未完成…"-如果
Task
在await
ed时已经完成,则async
方法将同步继续。因此,在这种情况下不需要捕获上下文 - "…默认情况下…"-这是默认行为,可以更改。特别是,调用
Task.ConfigureAwait
方法并为continueOnCapturedContext
参数传递false
。此方法返回一个不可重写的类型(而不是Task
(,如果其参数为false
,则该类型将不会在捕获的上下文中继续