为什么在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框架?

谢谢你提出的任何想法。

为什么在WPF UI线程中执行异步回调

原因是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 a Task…"-此行为是Task类型的await行为的一部分。其他类型可能会也可能不会进行类似的捕获
  • "…尚未完成…"-如果Taskawait ed时已经完成,则async方法将同步继续。因此,在这种情况下不需要捕获上下文
  • "…默认情况下…"-这是默认行为,可以更改。特别是,调用Task.ConfigureAwait方法并为continueOnCapturedContext参数传递false。此方法返回一个不可重写的类型(而不是Task(,如果其参数为false,则该类型将不会在捕获的上下文中继续