“异步等待”如何理解它

时间:2015-05-12 07:37:02

标签: c# .net

代码是:

    static void Main(string[] args)
    {
        Test();

        Console.WriteLine("Main Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);

        Console.Read();
    }

    static async void Test()
    {
        Console.WriteLine("before wait Current Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);

        await GetName();

        Console.WriteLine("after wait Current Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);
    }

    static async Task GetName()
    {
        await Task.Run(() =>
          {
              Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
              Console.WriteLine("In antoher thread.....");
          });
    }

结果是:

before wait Current Thread Id:9
Current Thread Id :10
In antoher thread.....
Main Thread Id :9       
after wait Current Thread Id:10  or  sometimes is  after wait Current Thread Id:11

我不知道为什么“主线程ID”运行速度快于“等待当前线程ID”之后。 为什么有三个ThreadID。

4 个答案:

答案 0 :(得分:3)

嗯,你可以很容易地追踪发生的事情。

主线程:

  1. 方法Test被称为
  2. 打印
  3. before wait
  4. 新任务在线程池上排队,Test立即返回Main
  5. 到目前为止,这是一个明确的同步序列。其余部分不是确定性的,因为理论上,Main的其余部分或线程池Task可以先执行 - 实际上,它们(理论上)也可以交错,Main Thread Id正在打印在Current thread IdIn another thread之间。

    但是,after wait将始终在 In another thread后打印

    您已经可以看到至少涉及两个线程 - 当您使用Task.Run时,这不应该令人惊讶。那么第三个来自哪里?好吧,await没有做任何魔法。在IL中没有魔法指令。所以它实际上做的只是安排你正在等待的Task的延续,以及方法的其余部分。由于没有同步上下文来重新编组延续,因此继续将简单地再次排队到线程池 - 采用另一个线程。但是,从理论上讲,可以允许继续在原始线程(即Task.Run中使用的线程)上同步运行,尽管在这种情况下我假设它只是原始线程的重用而不是同步延续。但是你可以完全确定一件事 - 它将从不在主线程上运行 - 除非你明确设置同步上下文并处理主线程上的编组任务而不是使用{{1} }用于最后的阻塞操作。

答案 1 :(得分:1)

这是因为Test以异步方式运行,您不会等待结果。

您可以执行此操作以等待结果:

Task t = new Task(Test);

t.Wait();

此外,您的async方法实际上应该返回Task的实例,因为void仅用于异步事件处理程序(MSDN)。

答案 2 :(得分:0)

控制台应用程序中的SynchronizationContext是一个新线程,因此async / await不会在此环境中按预期运行。试试WPF应用程序。

答案 3 :(得分:0)

让你的代码消失!

这个保持原样

static void Main(string[] args)
{
    Test();

    Console.WriteLine("Main Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);

    Console.Read();
}

这是一种异步方法。更具体地说,它是一种异步空洞方法,有点不稳定;你很快就会发现不同之处。作为常规方法,它看起来像这样:

static void Test()
{
    Console.WriteLine("before wait Current Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);

    Task getnameresult = GetName();

    Action<Task> continuation = (task) => {
      Console.WriteLine("after wait Current Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);
    }

    Task continuationresult = getnameresult.ContinueWith(continuation);
    //you are guaranteed that continuation will run on some (unspecified) thread after the taskgetnameresult completes

    return;

    //note that we do nothing with continuationresult, not even return it, so there is no way to know if it has completed.
}

GetName变为

static Task GetName()
{
    return Task.Run(() =>
      {
          Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
          Console.WriteLine("In antoher thread.....");
      });
}

在这里设置异步的方法是Task.ContinueWith:https://msdn.microsoft.com/en-us/library/dd270696%28v=vs.110%29.aspx和Task.Run:https://msdn.microsoft.com/en-us/library/hh195051%28v=vs.110%29.aspx

这些方法都没有对线程说太多,只是说它们可能是异步运行的。运行时可以并且将根据需要安排它们。任何有关其工作原理的知识都应被视为一个实现细节,您应该假设每次运行应用程序时都会有所不同(并且它会有所不同,正如其他一些答案所示,在控制台应用程序或WPF应用程序中)< / p>

那么这里发生了什么。好吧,首先,Main会致电Test()Test将在等待当前线程ID&#34;之前首先打印&#34;。然后它调用GetName()

该方法计划在将来某个未指定的时间内在某个未指定的线程上运行的操作,这将打印&#34;当前线程ID&#34;和&#34;在另一个线程.....&#34;,并返回表示该操作的任务,将控制权返还给Test

Test现在将创建一个新任务,将在未来的其他时间,但肯定在返回的任务之后,打印消息&#34;等待当前线程ID&#34;,并将控制权交还给MainMain现在将阻止其正在运行的线程,直到它从控制台读取内容。

所以,让我们把它们放在一起。我们肯定知道 &#34;等待之前#34;首先打印,这是一个简单的同步调用。

然后我们知道在那之后,&#34;当前线程&#34;将打印行,并且&#34;主要线程&#34;我们将打印一行,但我们对它们的运行顺序没有任何承诺,或者它们甚至可能同时运行。

在您的情况下,运行时确定它将运行打印&#34;当前线程&#34;先行。为什么?原因。 SynchronisationContext秒。但最重要的是,你不应该关心的东西。当您异步编程时,您将放弃对先运行的内容以及下一步运行的内容的控制。

然后下一个延续同样的事情。在&#34;主线程ID&#34;之间没有内在联系。打印出来的时候&#34;等待&#34;打印,只有&#34;等待&#34;必须在&#34;当前主题&#34;之后的某个时刻到来。线打印。

通过异步操作,您告诉运行时您不关心事情的运行顺序。在你告诉语言你并不特别关心他们在愚蠢的边界上运行哪个命令之后,试图推断哪个事物会先于哪个事物运行。