根据我的理解,async和await所做的主要事情之一是使代码易于编写和阅读-但使用它们是否等于生成后台线程来执行长时间的逻辑?

我目前正在尝试最基本的例子。我内联添加了一些注释。你能给我解释一下吗?

// I don't understand why this method must be marked as `async`.
private async void button1_Click(object sender, EventArgs e)
{
    Task<int> access = DoSomethingAsync();
    // task independent stuff here

    // this line is reached after the 5 seconds sleep from 
    // DoSomethingAsync() method. Shouldn't it be reached immediately? 
    int a = 1; 

    // from my understanding the waiting should be done here.
    int x = await access; 
}

async Task<int> DoSomethingAsync()
{
    // is this executed on a background thread?
    System.Threading.Thread.Sleep(5000);
    return 1;
}

当前回答

根据我的理解,async和await所做的主要事情之一是使代码易于编写和阅读。

它们是为了让异步代码易于编写和阅读。

这和生成后台线程来执行长时间逻辑是一样的吗?

一点也不。

//我不明白为什么这个方法必须被标记为'async'。

async关键字启用await关键字。所以任何使用await的方法都必须被标记为async。

// DoSomethingAsync()方法在5秒休眠后到达该行。难道不应该立即到达吗?

不会,因为异步方法默认情况下不会在另一个线程上运行。

//是否在后台线程上执行?

No.


你可能会发现我的async/await介绍很有用。官方MSDN文档也非常好(尤其是TAP部分),异步团队还发布了一个很好的FAQ。

其他回答

当使用async和await时,编译器在后台生成一个状态机。

下面是一个例子,我希望我能解释一些正在发生的高级细节:

public async Task MyMethodAsync()
{
    Task<int> longRunningTask = LongRunningOperationAsync();
    // independent work which doesn't need the result of LongRunningOperationAsync can be done here

    //and now we call await on the task 
    int result = await longRunningTask;
    //use the result 
    Console.WriteLine(result);
}

public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation 
{
    await Task.Delay(1000); // 1 second delay
    return 1;
}

好的,这里发生了什么:

Task<int> longRunningTask = LongRunningOperationAsync();开始执行LongRunningOperation 独立的工作完成了,假设主线程(线程ID = 1),然后等待longRunningTask到达。 现在,如果longRunningTask还没有完成,它仍在运行,MyMethodAsync()将返回到它的调用方法,因此主线程不会被阻塞。当longRunningTask完成时,来自ThreadPool的线程(可以是任何线程)将返回到MyMethodAsync()之前的上下文中并继续执行(在这种情况下将结果打印到控制台)。

第二种情况是longRunningTask已经完成执行,结果可用。当到达await longRunningTask时,我们已经有了结果,所以代码将继续在同一线程上执行。(在本例中将结果打印到控制台)。当然,对于上面的例子,情况并非如此,其中涉及到Task.Delay(1000)。

解释

下面是一个高层async/await的快速示例。除此之外,还有很多细节需要考虑。

注意:Task.Delay(1000)模拟工作1秒。我认为最好将此视为等待来自外部资源的响应。由于我们的代码正在等待响应,系统可以将正在运行的任务设置到一边,并在完成后返回到它。同时,它可以在该线程上做一些其他工作。

在下面的例子中,第一个块正是这样做的。它立即启动所有任务(Task。延迟线),并把它们放到一边。代码将在await一行上暂停,直到1秒的延迟完成,然后才进入下一行。由于b、c、d和e几乎与a同时开始执行(由于缺少await),因此在本例中它们应该大致同时完成。

在下面的例子中,第二个块正在启动一个任务,并在开始后续任务之前等待它完成(这就是await所做的)。每次迭代需要1秒。await是暂停程序并在继续之前等待结果。这是第一块和第二块的主要区别。

例子

Console.WriteLine(DateTime.Now);

// This block takes 1 second to run because all
// 5 tasks are running simultaneously
{
    var a = Task.Delay(1000);
    var b = Task.Delay(1000);
    var c = Task.Delay(1000);
    var d = Task.Delay(1000);
    var e = Task.Delay(1000);

    await a;
    await b;
    await c;
    await d;
    await e;
}

Console.WriteLine(DateTime.Now);

// This block takes 5 seconds to run because each "await"
// pauses the code until the task finishes
{
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
}
Console.WriteLine(DateTime.Now);

输出:

5/24/2017 2:22:50 PM
5/24/2017 2:22:51 PM (First block took 1 second)
5/24/2017 2:22:56 PM (Second block took 5 seconds)

关于SynchronizationContext的额外信息

注意:这就是我感到有点模糊的地方,所以如果我错了什么,请纠正我,我会更新答案。对它的工作原理有一个基本的了解是很重要的,但只要你从来没有使用过ConfigureAwait(false),你也可以成为这方面的专家,尽管我认为你可能会失去一些优化的机会。

有一个方面使得异步/等待概念有点难以掌握。事实上,在这个例子中,这一切都发生在同一个线程上(或者至少在SynchronizationContext方面看起来是同一个线程)。默认情况下,await将恢复运行它的原始线程的同步上下文。例如,在ASP中。NET中你有一个HttpContext,当请求进入时它被绑定到一个线程上。此上下文包含特定于原始Http请求的内容,例如原始request对象,其中包含语言、IP地址、报头等内容。如果你在处理过程中切换线程,你可能会在不同的HttpContext中尝试从这个对象中提取信息,这可能是灾难性的。如果您知道您不会将上下文用于任何事情,您可以选择“不关心”它。这基本上允许您的代码在单独的线程上运行,而无需带上下文。

你如何做到这一点?默认情况下,await a;代码实际上做了一个假设,你想要捕获和恢复上下文:

await a; //Same as the line below
await a.ConfigureAwait(true);

如果你想让主代码在没有原始上下文的情况下继续在一个新线程上运行,你只需使用false而不是true,这样它就知道它不需要恢复上下文。

await a.ConfigureAwait(false);

在程序暂停之后,它可能会继续在一个具有不同上下文的完全不同的线程上运行。这就是性能改进的来源——它可以在任何可用的线程上继续运行,而不必恢复它开始时的原始上下文。

这些东西让人困惑吗?地狱耶!你能算出来吗?可能!一旦你掌握了概念,然后转向Stephen Cleary的解释,它往往更适合那些已经对async/await有技术理解的人。

这个答案旨在提供一些特定于ASP.NET的信息。

通过在MVC控制器中使用async/await,可以提高线程池的利用率,并实现更好的吞吐量,如下文所述。

http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4

在web应用程序中看到大量的并发请求 启动或有突发负载(并发性突然增加), 使这些web服务调用异步将增加 应用程序的响应性。异步请求采用 处理同步请求所需的时间相同。例如, 如果一个请求进行了web服务调用,需要两秒钟才能完成 完成后,无论执行与否,请求都需要两秒钟 同步或异步。然而,在异步调用期间, 线程在运行时不会阻塞对其他请求的响应 等待第一个请求完成。因此,异步 请求阻止了请求队列和线程池的增长 调用长时间运行的操作的许多并发请求。

最好的例子在这里,请欣赏:

说实话,我仍然认为最好的解释是维基百科上关于未来和承诺的解释:http://en.wikipedia.org/wiki/Futures_and_promises

基本思想是,您拥有一个单独的异步执行任务的线程池。使用时。然而,对象承诺它将在某个时间执行操作,并在您请求时给您结果。这意味着当您请求结果并且还没有完成时,它将阻塞,否则将在线程池中执行。

从那里你可以优化事情:一些操作可以异步实现,你可以优化像文件IO和网络通信通过批处理后续请求和/或重新排序它们。我不确定这是否已经在微软的任务框架中-但如果不是,这将是我首先要添加的事情之一。

实际上,您可以在c# 4.0中实现这种未来模式。如果你想知道它到底是如何工作的,我可以推荐这个链接:http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/。但是,如果您自己开始使用它,您将注意到如果您想做所有很酷的事情,您确实需要语言支持——这正是微软所做的。