7

关于这段代码:

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

感谢您提交的任何想法。

4

3 回答 3

7

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

Stephen Toub 在一篇博客文章中对此进行了描述。

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

于 2013-04-25T06:17:00.603 回答
2

但我想知道“为什么?”

你自己已经回答了:

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

异步的全部意义在于使异步更易于使用 - 因此您可以编写实际上是异步的“看起来同步”的代码。这通常包括希望整个异步方法都停留在一个上下文(例如 UI 线程)中——当您需要等待某事时,只需“暂停”该方法(不阻塞 UI 线程)。

“我怎么能做同样的事?”

不清楚你在这里的意思。基本上,可等待模式的实现用于Task确定TaskScheduler.FromCurrentSynchronizationContext()在哪个调度程序上发布回调 - 除非您已调用ConfigureAwait(false)显式选择退出此行为。所以这就是它的管理方式......你是否可以“做同样的事情”取决于你想要做什么。

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

于 2013-04-25T06:13:09.050 回答
0

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

当您尚未完成时,默认情况下会捕获一个“上下文”,用于在完成时恢复该await方法。这个“上下文”是除非它是 null,在这种情况下它是。TaskTaskSynchronizationContext.Current TaskScheduler.Current

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

  • “当您await...” - 如果您手动安排继续,例如,使用Task.ContinueWith,则不会进行上下文捕获。您必须自己使用类似(SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext()).
  • “... awaita Task...” - 此行为是awaittypes行为Task一部分。其他类型可能会也可能不会进行类似的捕获。
  • “......尚未完成......” - 如果在Task它被编辑时已经完成await,该async方法将同步继续。所以在这种情况下不需要捕获上下文。
  • “...默认情况下...” - 这是默认行为,可以更改。特别是,调用方法Task.ConfigureAwait并传递参数。此方法返回一个等待类型(不是 a ),如果它的参数是 ,它将不会在捕获的上下文中继续。falsecontinueOnCapturedContextTaskfalse
于 2013-04-25T12:32:56.717 回答