10

我读过 Eric lippert 的文章,关于async, 以及人们对async关键字的困惑。他说 :

it( async) 表示“此方法包含涉及等待异步操作的控制流,因此将由编译器重写为继续传递样式,以确保异步操作可以在正确的位置恢复此方法。” 异步方法的全部意义在于您尽可能多地停留在当前线程上

我不明白这一点。如果我执行一个异步方法 ( Task) 并且它运行,它肯定会在另一个线程上运行。

此外,如果我编写一个方法使用await,(恕我直言)它会释放正常的控制流,并且代码会在稍后在另一个线程上以 类似的方式“重构”。ContinueWith

我用 (console) 测试了它:

/*1*/   public void StartChain()
/*2*/   {
/*3*/           var a = FuncA();
/*4*/           Console.WriteLine(a.Result);
/*5*/   }
/*6*/   
/*7*/   public async Task < int > FuncA()
/*8*/   {
/*9*/           Console.WriteLine("A--" + Thread.CurrentThread.ManagedThreadId);
/*10*/           var t = await FuncB();
/*11*/           Console.WriteLine("B--" + Thread.CurrentThread.ManagedThreadId);
/*12*/           return t;
/*13*/   }
/*14*/   
/*15*/   public async Task < int > FuncB()
/*16*/   {
/*17*/           Console.WriteLine("C--" + Thread.CurrentThread.ManagedThreadId);
/*18*/           await Task.Delay(2000);
/*19*/           Console.WriteLine("D--" + Thread.CurrentThread.ManagedThreadId);
/*20*/           return 999;
/*21*/   }
/*22*/   
/*23*/   void Main()
/*24*/   {
/*25*/           StartChain();
/*26*/   }
/*27*/   

结果是:

A--7
C--7
D--17         <-----D  and B are on different thread
B--17
999

那么 Eric 所说的“留在当前线程上”是什么意思?

编辑1:

其中asp.net还返回不同的线程 ID。

public async Task<int> FuncA()
{
    Response.Write("<br/>C----" + Thread.CurrentThread.ManagedThreadId);
    var t = await FuncB();
    Response.Write("<br/>D----" + Thread.CurrentThread.ManagedThreadId);
    return t;
}

public async Task<int> FuncB()
{
    Response.Write("<br/>E----" + Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(2000);
    Response.Write("<br/>F----" + Thread.CurrentThread.ManagedThreadId);
    return 999;
}



protected async void Page_Load(object sender, EventArgs e)
{
    Response.Write("<br/>A----" + Thread.CurrentThread.ManagedThreadId);
    var a=await FuncA();
    Response.Write("<br/>B----" + Thread.CurrentThread.ManagedThreadId);

}

A----8
C----8
E----8
F----9
D----9
B----9

编辑 2

(得到答案后)

似乎该线程仅在 GUI 应用程序中提供:。我在winform上运行这段代码

  public async Task<int> FuncA()
        {
            textBox1.Text +=Environment.NewLine+ "\nC----" + Thread.CurrentThread.ManagedThreadId;
            var t = await FuncB();
            textBox1.Text += Environment.NewLine + "\nD----" + Thread.CurrentThread.ManagedThreadId;
            return t;
        }

        public async Task<int> FuncB()
        {
            textBox1.Text += Environment.NewLine + "\nE----" + Thread.CurrentThread.ManagedThreadId;
            await Task.Delay(2000);
            textBox1.Text += Environment.NewLine + "\nF----" + Thread.CurrentThread.ManagedThreadId;
            return 999;
        }




        private async void Form1_Load(object sender, EventArgs e)
        {
            textBox1.Text += Environment.NewLine + "\nA----" + Thread.CurrentThread.ManagedThreadId;
            var a = await FuncA();
            textBox1.Text += Environment.NewLine + "\nB----" + Thread.CurrentThread.ManagedThreadId;
        }

在此处输入图像描述

4

3 回答 3

27

如果我执行一个异步方法并运行它,它肯定会在另一个线程上运行。

不,它通常在另一个线程上运行。它不一定在另一个线程上运行

暂时停止思考线程,想想异步的本质。异步的本质是:

  • 我有一些我目前正在执行的工作流程。
  • 在获得信息 X 之前,我无法继续此工作流程。
  • 在我得到信息 X 之前,我会做其他事情。
  • 在未来的某个时候,一旦我有了 X,我将回到我在工作流程中中断的地方并继续。

假设您正在纳税,并且在这个复杂的工作流程中,您有大量的附加工作要执行。你可以执行一些操作然后记住你在哪里,然后去吃午饭。然后回来再做几个操作,然后记住你在哪里,喂猫。然后回来再做几个操作,然后记住你在哪里,洗碗。然后完成计算,并从工作流程中断的地方继续。

这是一个异步计算,但它只需要一个工作人员来完成。拥有多个工人只是一种特别方便的异步方式,它不是必需的。

于 2013-07-16T19:23:26.997 回答
14

添加了 async/await 支持以帮助程序员编写不会冻结的 GUI。在商店应用程序中特别有用,并且它被添加到 C# v5 的核心原因是,WinRT 是一个非常不友好的 api,它有许多异步方法。

“保持在同一个线程上”场景在 GUI 应用程序中非常重要,这是必需的,因为 GUI 不是线程安全的。然而,它确实需要一个调度程序循环(又名 Application.Run),这是让异步代码在同一个线程上恢复的唯一方法。该循环是生产者-消费者问题的核心解决方案。

显然你的程序没有,看起来很像控制台模式应用程序。因此,您不会得到这种行为,它会在工作线程上恢复。

问题不大,您实际上并不需要它在同一个线程上恢复,因为无论如何控制台都是线程安全的。好吧,大多数情况下,当您要求输入时,不包括在 .NET 4.5 中添加的锁。当然,这也意味着您也没有大量用于 async/await 的任务,Task 也可以正常工作。

于 2013-07-15T19:04:01.547 回答
14

Eric Lippert's "thread" terminology is simplified. I have an async/await intro on my blog that explains how await will capture the current context and use that to resume the async method.

If you are in a UI context, then the context is the single UI thread, and the async method will resume on that thread. Otherwise, the rules are a bit more complicated. In particular, Console apps do not provide any context, so async methods by default resume on the thread pool.

于 2013-07-15T18:57:22.717 回答