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

时间:2013-04-25 06:02:27

标签: c# wpf asynchronous

关于这段代码:

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**

没关系:await之后的代码片段与任务本身在同一个线程中执行。

现在,如果我在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线程中执行await后看到代码。嗯,这很棒,因为它可以操纵可观察的集合等...但我想知道“为什么?” “我怎么能这样做?”这与某些TaskScheduler行为有关吗?这是在.NET Framework中进行硬编码的吗?

您可以提交的任何想法。

3 个答案:

答案 0 :(得分:7)

原因是当从UI线程启动任务时,Task.Run将捕获SynchronizationContext,如果它存在于WPF应用程序中。然后,任务将使用SynchronizationContext将回调序列化到UI线程。但是,如果在控制台应用程序中没有上下文可用,则回调将在不同的线程上发生。

Stephen Toub在blog entry中描述了这一点。

BTW,使用时要小心,不要在任务中使用Thread.Sleep。它可能会导致奇怪的行为,因为任务可能不会绑定到一个线程。请改用Task.Delay。

答案 1 :(得分:2)

  

但我想知道“为什么?”

你自己已经回答了这个问题:

  

嗯,这很棒,因为它可以操作可观察的集合等......

异步的全部意义在于使异步更容易使用 - 因此您可以编写实现异步的“同步查找”代码。这通常包括想要在整个异步方法的一个上下文(例如UI线程)中 - 只需“暂停”该方法(不阻止UI线程),当您需要等待某些东西时。

  

“我怎么能这样做?”

目前尚不清楚你的意思。基本上,Task的等待模式的实现使用TaskScheduler.FromCurrentSynchronizationContext()来计算发布回调的调度程序 - 除非您已调用ConfigureAwait(false)以明确选择退出此行为。这就是它如何管理它......你是否可以“做同样的事情”取决于你想要做的事情。

有关等待模式的更多详情,请参阅async/await FAQ中的“等待什么”问题。

答案 2 :(得分:0)

您可能会发现我的async/await intro有帮助。其他答案几乎正确。

当你await一个尚未完成的Task时,默认会捕获一个“上下文”,用于在Task完成时恢复该方法。此“上下文”为SynchronizationContext.Current ,除非它为空,在这种情况下为TaskScheduler.Current

请注意此操作所需的条件:

  • “当您await ...”时 - 如果您手动安排继续,例如使用Task.ContinueWith,则不会进行上下文捕获。你必须自己使用(SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext())
  • 之类的东西
  • “... await a Task ...” - 此行为是await类型Task行为的一部分。其他类型可能会也可能不会进行类似的捕获。
  • “......尚未完成...” - 如果Taskawait之前已经完成,async方法将继续同步。因此,在这种情况下无需捕获上下文。
  • “...默认情况下......” - 这是默认行为,可以更改。特别是,调用Task.ConfigureAwait方法并将false传递给continueOnCapturedContext参数。此方法返回一个等待类型(不是Task),如果其参数为false,则该类型不会在捕获的上下文中继续。