C# 如何以及何时使用“异步”和“等待”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14455293/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 11:51:36  来源:igfitidea点击:

How and when to use ‘async’ and ‘await’

c#.netasynchronousasync-await

提问by Dan Dinu

From my understanding one of the main things that asyncand awaitdo is to make code easy to write and read - but is using them equal to spawning background threads to perform long duration logic?

从主要的东西我的理解一个asyncawait要做的就是让代码易于读写-但使用它们等于生成后台线程来执行持续时间长的逻辑?

I'm currently trying out the most basic example. I've added some comments inline. Can you clarify it for me?

我目前正在尝试最基本的例子。我已经内联添加了一些评论。你能帮我澄清一下吗?

// 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;
}

采纳答案by Dan Dinu

When using asyncand awaitthe compiler generates a state machine in the background.

使用时asyncawait编译器在后台生成状态机。

Here's an example on which I hope I can explain some of the high-level details that are going on:

这是一个例子,我希望我可以解释一些正在发生的高级细节:

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;
}

OK, so what happens here:

好的,那么这里会发生什么:

  1. Task<int> longRunningTask = LongRunningOperationAsync();starts executing LongRunningOperation

  2. Independent work is done on let's assume the Main Thread (Thread ID = 1) then await longRunningTaskis reached.

    Now, if the longRunningTaskhasn't finished and it is still running, MyMethodAsync()will return to its calling method, thus the main thread doesn't get blocked. When the longRunningTaskis done then a thread from the ThreadPool (can be any thread) will return to MyMethodAsync()in its previous context and continue execution (in this case printing the result to the console).

  1. Task<int> longRunningTask = LongRunningOperationAsync();开始执行 LongRunningOperation

  2. 独立工作已完成,让我们假设主线程(线程 ID = 1)然后await longRunningTask到达。

    现在,如果longRunningTask尚未完成并且仍在运行,MyMethodAsync()则将返回其调用方法,因此主线程不会被阻塞。当longRunningTask随后完成从线程池线程(可以是任何线程)将返回MyMethodAsync()其先前的背景下,继续执行(在这种情况下,打印结果到控制台)。

A second case would be that the longRunningTaskhas already finished its execution and the result is available. When reaching the await longRunningTaskwe already have the result so the code will continue executing on the very same thread. (in this case printing result to console). Of course this is not the case for the above example, where there's a Task.Delay(1000)involved.

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

回答by Vnuk

I think you've picked a bad example with System.Threading.Thread.Sleep

我认为你选择了一个不好的例子 System.Threading.Thread.Sleep

Point of an asyncTask is to let it execute in background without locking the main thread, such as doing a DownloadFileAsync

asyncTask的点是让它在后台执行而不锁定主线程,比如做一个DownloadFileAsync

System.Threading.Thread.Sleepisn't something that is "being done", it just sleeps, and therefore your next line is reached after 5 seconds ...

System.Threading.Thread.Sleep不是“正在完成”的事情,它只是休眠,因此在 5 秒后到达您的下一行......

Read this article, I think it is a great explanation of asyncand awaitconcept: http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

阅读这篇文章,我认为这是一个很好的解释asyncawait概念:http: //msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

回答by Adriaan Stander

Further to the other answers, have a look at await (C# Reference)

除了其他答案,请查看await (C# Reference)

and more specifically at the example included, it explains your situation a bit

更具体地说,在包含的示例中,它稍微解释了您的情况

The following Windows Forms example illustrates the use of await in an async method, WaitAsynchronouslyAsync. Contrast the behavior of that method with the behavior of WaitSynchronously. Without an await operator applied to a task, WaitSynchronously runs synchronously despite the use of the async modifier in its definition and a call to Thread.Sleep in its body.

以下 Windows 窗体示例说明了 await 在异步方法 WaitAsynchronouslyAsync 中的使用。将该方法的行为与 WaitSynchronously 的行为进行对比。如果没有将 await 运算符应用于任务,WaitSynchronously 将同步运行,尽管在其定义中使用了 async 修饰符并在其主体中调用了 Thread.Sleep。

private async void button1_Click(object sender, EventArgs e)
{
    // Call the method that runs asynchronously.
    string result = await WaitAsynchronouslyAsync();

    // Call the method that runs synchronously.
    //string result = await WaitSynchronously ();

    // Display the result.
    textBox1.Text += result;
}

// The following method runs asynchronously. The UI thread is not
// blocked during the delay. You can move or resize the Form1 window 
// while Task.Delay is running.
public async Task<string> WaitAsynchronouslyAsync()
{
    await Task.Delay(10000);
    return "Finished";
}

// The following method runs synchronously, despite the use of async.
// You cannot move or resize the Form1 window while Thread.Sleep
// is running because the UI thread is blocked.
public async Task<string> WaitSynchronously()
{
    // Add a using directive for System.Threading.
    Thread.Sleep(10000);
    return "Finished";
}

回答by atlaste

To be honest I still think the best explanation is the one about future and promises on the Wikipedia: http://en.wikipedia.org/wiki/Futures_and_promises

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

The basic idea is that you have a separate pool of threads that execute tasks asynchronously. When using it. The object does however make the promise that it will execute the operation at some time and give you the result when you request it. This means that it will block when you request the result and hasn't finished, but execute in the thread pool otherwise.

基本思想是您有一个单独的线程池来异步执行任务。使用时。然而,该对象确实承诺它将在某个时间执行操作并在您请求时为您提供结果。这意味着它会在您请求结果并且尚未完成时阻塞,否则会在线程池中执行。

From there you can optimize things: some operations can be implemented async and you can optimize things like file IO and network communication by batching together subsequent requests and/or reordering them. I'm not sure if this is already in the task framework of Microsoft - but if it isn't that would be one of the first things I would add.

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

You can actually implement the future pattern sort-of with yields in C# 4.0. If you want to know how it works exactly, I can recommend this link that does a decent job: http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/. However, if you start toying with it yourself, you will notice that you really need language support if you want to do all the cool things -- which is exactly what Microsoft did.

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

回答by Stephen Cleary

From my understanding one of the main things that async and await do is to make code easy to write and read.

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

They're to make asynchronouscode easy to write and read, yes.

他们是为了使异步代码易于编写和阅读,是的。

Is it the same thing as spawning background threads to perform long duration logic?

它与生成后台线程以执行长时间逻辑是否相同?

Not at all.

一点也不。

// I don't understand why this method must be marked as 'async'.

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

The asynckeyword enables the awaitkeyword. So any method using awaitmust be marked async.

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

// This line is reached after the 5 seconds sleep from DoSomethingAsync() method. Shouldn't it be reached immediately?

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

No, because asyncmethods are not run on another thread by default.

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

// Is this executed on a background thread?

// 这是在后台线程上执行的吗?

No.

不。



You may find my async/awaitintrohelpful. The official MSDN docsare also unusually good (particularly the TAPsection), and the asyncteam put out an excellent FAQ.

您可能会发现我的async/await介绍很有帮助。该官方MSDN文档也异常丰厚(尤其是TAP部分),以及async团队做了一个很好的常见问题

回答by Lex Li

This answer aims to provide some info specific to ASP.NET.

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

By utilizing async/await in MVC controller, it is possible to increase thread pool utilization and achieve a much better throughput, as explained in the below article,

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

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

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

In web applications that sees a large number of concurrent requests at start-up or has a bursty load (where concurrency increases suddenly), making these web service calls asynchronous will increase the responsiveness of your application. An asynchronous request takes the same amount of time to process as a synchronous request. For example, if a request makes a web service call that requires two seconds to complete, the request takes two seconds whether it is performed synchronously or asynchronously. However, during an asynchronous call, a thread is not blocked from responding to other requests while it waits for the first request to complete. Therefore, asynchronous requests prevent request queuing and thread pool growth when there are many concurrent requests that invoke long-running operations.

在启动时看到大量并发请求或具有突发负载(并发性突然增加)的 Web 应用程序中,使这些 Web 服务调用异步将提高应用程序的响应能力。异步请求的处理时间与同步请求的处理时间相同。例如,如果请求进行需要两秒钟才能完成的 Web 服务调用,则无论是同步执行还是异步执行,该请求都需要两秒钟。但是,在异步调用期间,线程在等待第一个请求完成时不会被阻止响应其他请求。因此,当有许多并发请求调用长时间运行的操作时,异步请求会阻止请求排队和线程池增长。

回答by MarkWalls

Here is a quick console program to make it clear to those who follow. The TaskToDomethod is your long running method that you want to make async. Making it run async is done by the TestAsyncmethod. The test loops method just runs through the TaskToDotasks and runs them async. You can see that in the results because they don't complete in the same order from run to run - they are reporting to the console UI thread when they complete. Simplistic, but I think the simplistic examples bring out the core of the pattern better than more involved examples:

这是一个快速的控制台程序,可以让关注的人一目了然。该TaskToDo方法是您想要异步的长时间运行的方法。使其异步运行是由TestAsync方法完成的。测试循环方法只运行TaskToDo任务并异步运行它们。您可以在结果中看到这一点,因为它们从运行到运行的顺序不同——它们在完成时向控制台 UI 线程报告。简单化,但我认为简单化的例子比更复杂的例子更好地展示了模式的核心:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestingAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            TestLoops();
            Console.Read();
        }

        private static async void TestLoops()
        {
            for (int i = 0; i < 100; i++)
            {
                await TestAsync(i);
            }
        }

        private static Task TestAsync(int i)
        {
            return Task.Run(() => TaskToDo(i));
        }

        private async static void TaskToDo(int i)
        {
            await Task.Delay(10);
            Console.WriteLine(i);
        }
    }
}

回答by sppc42

Showing the above explanations in action in a simple console program:

在一个简单的控制台程序中显示上面的解释:

class Program
{
    static void Main(string[] args)
    {
        TestAsyncAwaitMethods();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }

    public async static void TestAsyncAwaitMethods()
    {
        await LongRunningMethod();
    }

    public static async Task<int> LongRunningMethod()
    {
        Console.WriteLine("Starting Long Running method...");
        await Task.Delay(5000);
        Console.WriteLine("End Long Running method...");
        return 1;
    }
}

And the output is:

输出是:

Starting Long Running method...
Press any key to exit...
End Long Running method...

Thus,

因此,

  1. Main starts the long running method via TestAsyncAwaitMethods. That immediately returns without halting the current thread and we immediately see 'Press any key to exit' message
  2. All this while, the LongRunningMethodis running in the background. Once its completed, another thread from Threadpool picks up this context and displays the final message
  1. Main 通过 启动长时间运行的方法TestAsyncAwaitMethods。立即返回而不停止当前线程,我们立即看到“按任意键退出”消息
  2. 所有这一切,LongRunningMethod都在后台运行。完成后,来自 Threadpool 的另一个线程获取此上下文并显示最终消息

Thus, not thread is blocked.

因此,不是线程被阻塞。

回答by Dmitry G.

is using them equal to spawning background threads to perform long duration logic?

使用它们是否等于产生后台线程来执行长时间的逻辑?

This article MDSN:Asynchronous Programming with async and await (C#)explains it explicitly:

这篇文章MDSN:Asynchronous Programming with async and await (C#)明确解释了它:

The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active.

async 和 await 关键字不会导致创建额外的线程。异步方法不需要多线程,因为异步方法不在自己的线程上运行。该方法在当前同步上下文上运行,并且仅当该方法处于活动状态时才在线程上使用时间。

回答by Joe Phillips

Explanation

解释

Here is a quick example of async/awaitat a high level. There are a lot more details to consider beyond this.

这是async/await在高级别的快速示例。除此之外,还有很多细节需要考虑。

Note: Task.Delay(1000)simulates doing work for 1 second. I think it's best to think of this as waiting for a response from an external resource. Since our code is waiting for a response, the system can set the running task off to the side and come back to it once it's finished. Meanwhile, it can do some other work on that thread.

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

In the example below, the first blockis doing exactly that. It starts all the tasks immediately (the Task.Delaylines) and sets them off to the side. The code will pause on the await aline until the 1 second delay is done before going to the next line. Since b, c, d, and eall started executing at almost the exact same time as a(due to lack of the await), they should finish at roughly the same time in this case.

在下面的示例中,第一个块就是这样做的。它立即启动所有任务(Task.Delay行)并将它们放在一边。代码将在该await a行暂停,直到 1 秒延迟完成,然后才能转到下一行。由于bcde几乎在与a(由于缺少等待)完全相同的时间开始执行,因此在这种情况下它们应该大致在同一时间完成。

In the example below, the second blockis starting a task and waiting for it to finish (that is what awaitdoes) before starting the subsequent tasks. Each iteration of this takes 1 second. The awaitis pausing the program and waiting for the result before continuing. This is the main difference between the first and second blocks.

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

Example

例子

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);

OUTPUT:

输出:

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)


Extra info regarding SynchronizationContext

关于 SynchronizationContext 的额外信息

Note: This is where things get a little foggy for me, so if I'm wrong on anything, please correct me and I will update the answer. It's important to have a basic understanding of how this works but you can get by without being an expert on it as long as you never use ConfigureAwait(false), although you will likely lose out on some opportunity for optimization, I assume.

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

There is one aspect of this which makes the async/awaitconcept somewhat trickier to grasp. That's the fact that in this example, this is all happening on the same thread (or at least what appears to be the same thread in regards to its SynchronizationContext). By default, awaitwill restore the synchronization context of the original thread that it was running on. For example, in ASP.NET you have an HttpContextwhich is tied to a thread when a request comes in. This context contains things specific to the original Http request such as the original Request object which has things like language, IP address, headers, etc. If you switch threads halfway through processing something, you could potentially end up trying to pull information out of this object on a different HttpContextwhich could be disastrous. If you know you won't be using the context for anything, you can choose to "not care" about it. This basically allows your code to run on a separate thread without bringing the context around with it.

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

How do you achieve this? By default, the await a;code actually is making an assumption that you DO want to capture and restore the context:

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

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

If you want to allow the main code to continue on a new thread without the original context, you simply use false instead of true so it knows it doesn't need to restore the context.

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

await a.ConfigureAwait(false);

After the program is done being paused, it will continue potentiallyon an entirely different thread with a different context. This is where the performance improvement would come from -- it could continue on on any available thread without having to restore the original context it started with.

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

Is this stuff confusing? Hell yeah! Can you figure it out? Probably! Once you have a grasp of the concepts, then move on to Stephen Cleary's explanations which tend to be geared more toward someone with a technical understanding of async/awaitalready.

这个东西很混乱吗?该死的!你能弄清楚吗?大概!一旦您掌握了这些概念,就可以转到 Stephen Cleary 的解释,这些解释往往更适合已经对async/有技术理解的人await