如何以及何时使用“async”和“await”

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

提问人:Dan Dinu 提问时间:1/22/2013 最后编辑:RoyDan Dinu 更新时间:8/11/2023 访问量:1216839

问:

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

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

// 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;
}
C# .NET 异步 async-await

评论

79赞 Eric Lippert 11/15/2013
此外,在您的示例中,请注意,在编译上述代码时会收到警告。注意警告。它告诉你这个代码没有意义。
4赞 Maxi Wu 6/4/2021
简短的回答可能会有所帮助。async/await 是基于单线程事件的模型。这允许您无序运行代码,直到等待代码行。
5赞 Matt W 6/11/2021
@stephen-Cleary的帖子,在他回答以下问题时,他还没有写: blog.stephencleary.com/2013/11/there-is-no-thread.html
0赞 Brad Gilbert 3/12/2022
在 Raku 中,它实际上会在 ..哪个会打印.然后在 5 秒后.紧随其后的是 .awaitsub example { my $p = do-something-async; say 'next line'; await $p; say 'done awaiting'}; sub do-something-async { return Promise.in(5).then: {say 'promise done'}}; example()next linepromise donedone awaiting
0赞 Timo 5/28/2022
据我了解,async/await 独立于它的使用位置 - 客户端或服务器。

答:

54赞 Vnuk 1/22/2013 #1

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

任务的要点是让它在后台执行而不锁定主线程,比如做一个asyncDownloadFileAsync

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

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

评论

7赞 Abdurrahim 2/2/2019
为什么睡眠是坏例子,但下载是好例子。当我看到 Thread.Sleep 时,就像 FooBar 一样,我知道有些任务需要时间。我认为他的问题很有意义
4赞 Gabriel Luci 9/11/2019
@Abdurrahim阻塞线程(线程只能空闲,不能执行任何其他操作),但异步方法不会。在 的情况下,线程可以执行其他操作,直到远程服务器发出回复。对于异步方法中的“一些需要时间的任务”,一个更好的占位符是 ,因为它实际上是异步的。Thread.SleepDownloadFileAsyncTask.Delay
2赞 Abdurrahim 9/12/2019
@GabrielLuci我的反对意见不是关于延迟与睡眠;你的答案看起来更像是一个稻草人的答案;如果你把这个作为对这个问题的评论,那我没有什么可以反对的,但作为一个答案,它闻起来更像是一个稻草人的答案。我认为在那里使用异步仍然是可以的,即使他/她必须进行的所有调用都会阻止调用;它不会使所有目的无效......即使剩下的都是句法糖,它算作一个有效的案例,
2赞 Gabriel Luci 9/12/2019
这不是我的答案。但要解决你的观点:这取决于方法的目的。如果他只是想找一种方法来调用,他就成功了。但在这种情况下,他试图制作一个异步运行的方法。他只用了关键词就做到了这一点。但是他的方法仍然是同步运行的,这个答案完美地解释了原因:因为他实际上没有运行任何异步代码。标记为的方法仍然同步运行,直到您不完整。如果没有,则该方法同步运行,编译器将警告您。asyncasyncawaitTaskawait
163赞 Adriaan Stander 1/22/2013 #2

除了其他答案之外,请查看 await(C# 参考)

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

下面的 Windows 窗体示例阐释了 await 在 async 方法 WaitAsynchronouslyAsync。对比其行为 方法,其行为为 WaitSynchronously。无需等待 运算符,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";
}

评论

4赞 Dan Dinu 1/22/2013
谢谢你的回答。但是 WaitAsynchronouslyAsync() 是在单独的线程上执行的吗?
37赞 Adriaan Stander 1/22/2013
我确实是这样,从“await 表达式不会阻止它正在执行的线程”部分开始。相反,它会导致编译器将异步方法的其余部分注册为等待任务的延续。然后,控制权返回给异步方法的调用方。任务完成后,它将调用其延续,并且异步方法的执行将从中断的位置继续执行。
15赞 Vimes 4/13/2013
根据这篇 MSDN 文章,“async 和 await 关键字不会导致创建其他线程......异步方法不会在自己的线程上运行”。我的理解是,在 await 关键字处,框架会向前跳过(返回调用方),以允许所有可能的独立代码在等待长时间操作完成时运行。我认为这意味着一旦所有独立代码都运行完毕,如果长操作没有返回,它将被阻塞。不过,我现在才刚刚学习这个。
12赞 MgSam 11/15/2013
@astander 这是不正确的。它不会在不同的线程上执行。它只是安排在火灾使用的计时器时调用的延续(方法的其余部分)。Task.Delay
2赞 Jared Updike 3/1/2014
这个答案是错误的,因为睡眠。使用 await Task.Delay(1000) 查看已接受的答案;具有正确的行为。
12赞 atlaste 1/22/2013 #3

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

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

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

实际上,您可以在 C# 4.0 中实现具有产量的未来模式排序。如果你想知道它到底是如何工作的,我可以推荐这个做得很好的链接: http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/ .然而,如果你自己开始玩它,你会注意到,如果你想做所有很酷的事情,你真的需要语言支持 - 这正是Microsoft所做的。

228赞 Stephen Cleary 1/22/2013 #4

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

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

这与生成后台线程来执行长时间逻辑是一回事吗?

一点也不。

我不明白为什么必须将此方法标记为“异步”。

关键字启用关键字。因此,任何使用的方法都必须标记。asyncawaitawaitasync

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

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

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

不。


你可能会发现我的 async/await 介绍很有帮助。官方的MSDN文档也异常出色(尤其是TAP部分),该团队发布了一个出色的FAQasync

评论

13赞 Roman Plášil 8/19/2013
所以它不是在后台线程上运行,但它也不会阻塞。这是可能的,因为异步 API 使用回调而不是处理线程。启动(I/O、套接字等)操作,然后返回执行操作。操作完成后,操作系统将调用回调。这就是Node.js或Python Twisted框架所做的,他们也有一些很好的解释。
4赞 Stanislav 1/14/2014
“async 关键字启用 await 关键字。因此,任何使用 await 的方法都必须标记为 async.“, - 但为什么呢?此答案无助于理解为什么必须将方法标记为异步。编译器不能通过在内部查找 await 关键字来推断该方法是异步的吗?
11赞 Stephen Cleary 1/15/2014
@Stanislav:我有一篇博客文章解决了这个问题。
7赞 DavidRR 5/28/2016
建议澄清:否,因为默认情况下方法不会在另一个线程上运行。在您的示例中,DoSomethingAsync() 中的 Sleep() 调用会阻止当前线程,这会阻止在 button1_Click() 中继续执行,直到 DoSomethingAsync() 完成。请注意,虽然 Thread.Sleep() 会阻止正在执行的线程,但 Task.Delay() 不会。async
5赞 Stephen Cleary 6/22/2020
@PeterLarsen'CPH':我在这里的回答是“异步与生成线程不同”、“默认情况下异步方法不在另一个线程上运行”和“在一个方法中不运行在另一个线程上”。所有这些都是正确的,文档也同意。Sleepasync
952赞 Dan Dinu 11/15/2013 #5

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

下面是一个示例,我希望我能解释一些正在进行的高级细节:

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

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

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

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

    现在,如果尚未完成并且仍在运行,将返回其调用方法,因此主线程不会被阻塞。完成后,ThreadPool 中的线程(可以是任何线程)将返回到其先前的上下文中并继续执行(在本例中,将结果打印到控制台)。longRunningTaskMyMethodAsync()longRunningTaskMyMethodAsync()

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

评论

92赞 Benison Sam 3/9/2015
为什么我们在 LongRunningOperation 异步方法中有一个带有“Task.Delay(1000);”的“await”?
9赞 Camilo Martinez 4/23/2015
@codea 在 Eric Lippert 对这篇文章的评论中,他将一篇介绍性文章链接到了这个主题,其中他专门将 DoEvents 策略与异步等待进行了比较
23赞 shelbypereira 12/4/2015
@BenisonSam线程有点旧,但我有同样的问题,一直在寻找答案。“await”的原因是,如果我们省略“await”,LongRunningOperationAsync() 将立即返回。事实上,如果我们删除 await,编译器会发出警告。Stephen Cleary的博客文章 blog.stephencleary.com/2011/09/...对设计讨论进行了精彩的介绍。
92赞 Bruno Santos 5/19/2016
如果每个异步方法都需要在其中有一个 await,并且 await 只能在具有 async 的方法上完成,那么它什么时候停止?
156赞 Krishna Deepak 8/16/2016
这个答案显然是错误的。这些如此多的点赞会给很多用户造成错误的理解。MS 文档清楚地表明,仅使用 async 时不使用其他线程,await 。msdn.microsoft.com/en-us/library/mt674882.aspx请有人纠正答案。因此,我浪费了一整天。
18赞 Lex Li 9/8/2014 #6

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

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

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

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

27赞 MarkWalls 7/17/2015 #7

这是一个快速控制台程序,可以让关注者一目了然。该方法是要设置为异步的长时间运行的方法。使其异步运行是通过该方法完成的。test loops 方法只是运行任务并异步运行它们。你可以在结果中看到这一点,因为它们在运行到运行之间没有以相同的顺序完成 - 它们在完成时会向控制台 UI 线程报告。很简单,但我认为简单的例子比更复杂的例子更能体现出模式的核心:TaskToDoTestAsyncTaskToDo

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);
        }
    }
}
76赞 sppc42 8/13/2015 #8

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

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

输出为:

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

因此

  1. Main 通过 启动长时间运行的方法。它会立即返回而不会停止当前线程,并且我们立即看到“按任意键退出”消息TestAsyncAwaitMethods
  2. 一直以来,都在后台运行。完成后,Threadpool 中的另一个线程将获取此上下文并显示最终消息LongRunningMethod

因此,没有线程被阻塞。

评论

0赞 Saghachi 11/23/2018
“按任意键退出...”将显示在输出的哪一部分?
1赞 Saghachi 11/23/2018
(返回 1)的用法是什么?有必要吗?
1赞 Kuba Do 10/28/2019
@StudioX我认为它必须具有返回类型整数
1赞 Christiano Kiss 1/17/2020
我认为这部分值得进一步解释:关键字允许您直接返回 的基础类型,从而更容易使现有代码适应 await/async 世界。但是您不必返回值,因为可以在不指定返回类型的情况下返回 a,这相当于同步方法。请注意,C# 允许使用方法,但除非您正在处理事件处理程序,否则应避免这样做。return 1awaitTask<T>Taskvoidasync void
2赞 Dmitry G. 10/5/2016 #9

使用它们等于生成后台线程来执行长 持续时间逻辑?

本文 MDSN:Asynchronous Programming with async and await (C#) 对此进行了明确解释:

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

220赞 Joe Phillips 5/26/2017 #10

解释

这是一个 / 在高层次上的快速示例。除此之外,还有很多细节需要考虑。asyncawait

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

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

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

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)

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

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

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

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

await a.ConfigureAwait(false);

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

这些东西令人困惑吗?地狱耶!你能弄清楚吗?可能!一旦你掌握了这些概念,那么继续研究 Stephen Cleary 的解释,这些解释往往更适合那些对 / 已经有技术理解的人。asyncawait

评论

1赞 vkg 6/9/2017
假设如果所有这些任务都返回一个 int,如果我在第二个任务(或一些计算)中使用第一个任务的结果,那会是错误的吗?
4赞 Joe Phillips 6/9/2017
@veerendragupta是的。在这种情况下,你会有意识地选择不异步运行它们(因为它们不是异步的)。关于配置上下文,还有一些其他事情需要了解,我不会在这里讨论
0赞 Vitani 1/24/2018
那么绝对是浪费吗?你不妨去掉 /?await MethodCall()awaitasync
5赞 Joe Phillips 1/24/2018
@Jocie 不完全是。当您调用 时,我认为它会将线程释放回池中,而不是保留它。这使得它可以在等待任务返回时在其他地方使用await
4赞 Bob Horn 3/11/2018
@JoePhillips 我认为你刚才说的就是 async/await 的本质。调用线程被释放,可供计算机上的其他进程使用。等待调用完成后,将使用一个新线程来恢复调用方最初启动的内容。调用方仍在等待,但这样做的好处是在此期间释放了线程。这就是异步/等待的好处吗?
5赞 user21306 6/3/2017 #11

我的理解也是,应该添加第三个术语:.Task

Async只是您在方法上放置的限定符,表示它是一个异步方法。

Task是函数的返回值。它异步执行。async

你是一个任务。当代码执行到达此行时,控制权会跳回周围原始函数的调用方。await

相反,如果将函数(即)的返回值分配给变量,则当代码执行到达此行时,它只会在异步执行继续经过周围函数中的该行。asyncTaskTask

22赞 Tone Škoda 7/15/2017 #12

这里的所有答案都使用或其他一些内置函数。但这是我的示例,它们不使用这些函数:Task.Delay()asyncasync

// Starts counting to a large number and then immediately displays message "I'm counting...". 
// Then it waits for task to finish and displays "finished, press any key".
static void asyncTest ()
{
    Console.WriteLine("Started asyncTest()");
    Task<long> task = asyncTest_count();
    Console.WriteLine("Started counting, please wait...");
    task.Wait(); // if you comment this line you will see that message "Finished counting" will be displayed before we actually finished counting.
    //Console.WriteLine("Finished counting to " + task.Result.ToString()); // using task.Result seems to also call task.Wait().
    Console.WriteLine("Finished counting.");
    Console.WriteLine("Press any key to exit program.");
    Console.ReadLine();
}

static async Task<long> asyncTest_count()
{
    long k = 0;
    Console.WriteLine("Started asyncTest_count()");
    await Task.Run(() =>
    {
        long countTo = 100000000;
        int prevPercentDone = -1;
        for (long i = 0; i <= countTo; i++)
        {
            int percentDone = (int)(100 * (i / (double)countTo));
            if (percentDone != prevPercentDone)
            {
                prevPercentDone = percentDone;
                Console.Write(percentDone.ToString() + "% ");
            }

            k = i;
        }
    });
    Console.WriteLine("");
    Console.WriteLine("Finished asyncTest_count()");
    return k;
}

评论

4赞 Jeffnl 7/27/2018
谢谢!第一个答案实际上做了一些工作,而不是等待。
1赞 encoder 12/30/2018
感谢您的展示以及如何使用它来避免异步/等待地狱:Ptask.Wait();
1赞 repeatdomiau 2/10/2021
@encoder task.wait() 和 async/await 不是同一个概念,请小心。一种适用于并行编程线程同步,另一种适用于异步编程线程释放。他们是 oposite。wait() 块,await 避免阻塞...问题是 C# 使用 Task 来表示它们......所以你可以用错的......
0赞 repeatdomiau 2/10/2021
这个遮阳篷展示了一个伪装成异步的并行编程(处理密集型工作)的用例。@Jeffnl,异步的创建是为了等待,而不是做其他事情,它不是并行编程。看看我的anwser,它可能会变得更清楚,但是await用于在等待不需要处理的东西时释放线程,例如磁盘读/写,数据库查询,API调用等...该发布线程可以执行其他工作,但不能在同一代码中执行,可能在桌面中的另一个请求 (Web) 或进程中。结果完成后,同一线程或另一个线程将恢复执行。
12赞 vibs2006 1/24/2018 #13

请参阅此小提琴 https://dotnetfiddle.net/VhZdLU(如果可能,请对其进行改进)以运行一个简单的控制台应用程序,该应用程序显示了同一程序中 Task、Task.WaitAll()、async 和 await 运算符的用法。

这把小提琴应该清除你的执行周期概念。

下面是示例代码

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {               
        var a = MyMethodAsync(); //Task started for Execution and immediately goes to Line 19 of the code. Cursor will come back as soon as await operator is met       
        Console.WriteLine("Cursor Moved to Next Line Without Waiting for MyMethodAsync() completion");
        Console.WriteLine("Now Waiting for Task to be Finished");       
        Task.WaitAll(a); //Now Waiting      
        Console.WriteLine("Exiting CommandLine");       
    }

    public static async Task MyMethodAsync()
    {
        Task<int> longRunningTask = LongRunningOperation();
        // independent work which doesn't need the result of LongRunningOperationAsync can be done here
        Console.WriteLine("Independent Works of now executes in MyMethodAsync()");
        //and now we call await on the task 
        int result = await longRunningTask;
        //use the result 
        Console.WriteLine("Result of LongRunningOperation() is " + result);
    }

    public static async Task<int> LongRunningOperation() // assume we return an int from this long running operation 
    {
        Console.WriteLine("LongRunningOperation() Started");
        await Task.Delay(2000); // 2 second delay
        Console.WriteLine("LongRunningOperation() Finished after 2 Seconds");
        return 1;
    }   

}

来自输出窗口的跟踪: enter image description here

评论

0赞 Junaid 1/31/2022
这让我更加困惑。
0赞 Oluwadamilola Adegunwa 3/26/2023
你撒谎了!(或者这根本不完整!您没有显示 Task、Task.WaitAll() 的用法(我检查了小提琴 - 也不在那里!
4赞 Weslley Rufino de Lima 2/22/2018 #14
public static void Main(string[] args)
{
    string result = DownloadContentAsync().Result;
    Console.ReadKey();
}

// You use the async keyword to mark a method for asynchronous operations.
// The "async" modifier simply starts synchronously the current thread. 
// What it does is enable the method to be split into multiple pieces.
// The boundaries of these pieces are marked with the await keyword.
public static async Task<string> DownloadContentAsync()// By convention, the method name ends with "Async
{
    using (HttpClient client = new HttpClient())
    {
        // When you use the await keyword, the compiler generates the code that checks if the asynchronous operation is finished.
        // If it is already finished, the method continues to run synchronously.
        // If not completed, the state machine will connect a continuation method that must be executed WHEN the Task is completed.


        // Http request example. 
        // (In this example I can set the milliseconds after "sleep=")
        String result = await client.GetStringAsync("http://httpstat.us/200?sleep=1000");

        Console.WriteLine(result);

        // After completing the result response, the state machine will continue to synchronously execute the other processes.


        return result;
    }
}
0赞 Stephen Marcus 4/10/2018 #15

此处的答案可作为有关 await/async 的一般指导。它们还包含有关如何连接 await/async 的一些详细信息。我想和大家分享一些在使用此设计模式之前应该了解的实践经验。

术语“await”是字面意思,因此无论您在哪个线程上调用它,都会等待方法的结果,然后再继续。在前台线程上,这是一场灾难。前台线程承担着构建应用的负担,包括视图、视图模型、初始动画以及与这些元素一起启动的任何其他内容。因此,当您等待前台线程时,您将停止应用程序。用户等待并等待,当似乎没有任何发生时。这提供了负面的用户体验。

您当然可以使用多种方式等待后台线程:

Device.BeginInvokeOnMainThread(async () => { await AnyAwaitableMethod(); });

// Notice that we do not await the following call, 
// as that would tie it to the foreground thread.
try
{
Task.Run(async () => { await AnyAwaitableMethod(); });
}
catch
{}

这些备注的完整代码位于 https://github.com/marcusts/xamarin-forms-annoyances。请参阅名为 AwaitAsyncAntipattern.sln 的解决方案。

GitHub 站点还提供了有关此主题的更详细讨论的链接。

评论

1赞 geometrikal 6/11/2018
据我了解,是回调的语法糖,它与线程无关。msdn.microsoft.com/en-us/magazine/hh456401.aspx它适用于非 CPU 绑定代码,例如等待输入或延迟。 应该只用于 CPU 密集型代码 blog.stephencleary.com/2013/10/...async / awaitTask.Run
1赞 Don Cheadle 7/2/2018
The term "await" is literal, so whatever thread you call it on will wait for the result of the method before continuing.这不是真的 - 也许你的意思是 Task.Wait()?当您使用 时,它会将方法的其余部分设置为在您等待完成的任何内容时执行的延续。它将退出使用方法的方法,以便调用方可以继续。然后,当 await-ed 行实际完成时,它会在某个线程(通常是工作线程)上完成该方法的其余部分。await
1赞 Don Cheadle 7/2/2018
@geometrikal的核心是释放 .NET 线程。当您执行真正的异步操作(例如 .NET 的 File.WriteAsync),它会挂起你使用的方法的其余部分,以便调用方可以继续并可能完成其目的。没有线程阻塞或等待 -ed 操作。当您完成的操作完成后,该方法的其余部分将放在线程上并执行(类似于回调想法)。async/awaitawaitawaitawaitawaitasync/await
0赞 repeatdomiau 2/10/2021
对不起,伙计,但这是不对的...... @DonCheadle指出了错误,Task.wait 与 async/awawait 不同。并行编程和异步编程之间存在很多混淆。Wait 或 WaitAll 块用于同步并行运行的线程,async/await 可在等待时释放线程以执行其他工作。
0赞 repeatdomiau 2/10/2021
我相信反模式是异步/等待同步的东西,这没有意义......您应该只等待异步本机方法。为了指出这并不仅适用于 IO,我将使用 Entity Framework 的 saveAsync 或 toListAsync(仍然是 IO,但通常在另一台服务器中)或使用异步请求方法进行的 API 调用作为示例。
2赞 Zaheer Abbas 4/26/2018 #16

下面是通过打开对话框读取 excel 文件,然后使用 async 并等待异步运行代码的代码,该代码从 excel 中逐行读取并绑定到网格

namespace EmailBillingRates
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            lblProcessing.Text = "";
        }

        private async void btnReadExcel_Click(object sender, EventArgs e)
        {
            string filename = OpenFileDialog();

            Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
            Microsoft.Office.Interop.Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(filename);
            Microsoft.Office.Interop.Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];
            Microsoft.Office.Interop.Excel.Range xlRange = xlWorksheet.UsedRange;
            try
            {
                Task<int> longRunningTask = BindGrid(xlRange);
                int result = await longRunningTask;

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
            finally
            {
                //cleanup  
               // GC.Collect();
                //GC.WaitForPendingFinalizers();

                //rule of thumb for releasing com objects:  
                //  never use two dots, all COM objects must be referenced and released individually  
                //  ex: [somthing].[something].[something] is bad  

                //release com objects to fully kill excel process from running in the background  
                Marshal.ReleaseComObject(xlRange);
                Marshal.ReleaseComObject(xlWorksheet);

                //close and release  
                xlWorkbook.Close();
                Marshal.ReleaseComObject(xlWorkbook);

                //quit and release  
                xlApp.Quit();
                Marshal.ReleaseComObject(xlApp);
            }

        }

        private void btnSendEmail_Click(object sender, EventArgs e)
        {

        }

        private string OpenFileDialog()
        {
            string filename = "";
            OpenFileDialog fdlg = new OpenFileDialog();
            fdlg.Title = "Excel File Dialog";
            fdlg.InitialDirectory = @"c:\";
            fdlg.Filter = "All files (*.*)|*.*|All files (*.*)|*.*";
            fdlg.FilterIndex = 2;
            fdlg.RestoreDirectory = true;
            if (fdlg.ShowDialog() == DialogResult.OK)
            {
                filename = fdlg.FileName;
            }
            return filename;
        }

        private async Task<int> BindGrid(Microsoft.Office.Interop.Excel.Range xlRange)
        {
            lblProcessing.Text = "Processing File.. Please wait";
            int rowCount = xlRange.Rows.Count;
            int colCount = xlRange.Columns.Count;

            // dt.Column = colCount;  
            dataGridView1.ColumnCount = colCount;
            dataGridView1.RowCount = rowCount;

            for (int i = 1; i <= rowCount; i++)
            {
                for (int j = 1; j <= colCount; j++)
                {
                    //write the value to the Grid  
                    if (xlRange.Cells[i, j] != null && xlRange.Cells[i, j].Value2 != null)
                    {
                         await Task.Delay(1);
                         dataGridView1.Rows[i - 1].Cells[j - 1].Value =  xlRange.Cells[i, j].Value2.ToString();
                    }

                }
            }
            lblProcessing.Text = "";
            return 0;
        }
    }

    internal class async
    {
    }
}
6赞 ABajpai 8/6/2018 #17

在更高的层次上:

1) Async 关键字启用 await,仅此而已。Async 关键字不会在单独的线程中运行该方法。beginning f async 方法同步运行,直到它在一个耗时的任务上命中 await。

2)您可以等待返回Task或T类型的Task的方法。您不能等待异步 void 方法。

3)当主线程在耗时的任务中遇到等待的时刻或实际工作开始时,主线程返回给当前方法的调用者。

4) 如果主线程看到仍在执行的任务上的等待,它不会等待它并返回给当前方法的调用者。这样,应用程序将保持响应状态。

5)等待处理任务,现在将在线程池中的单独线程上执行。

6)当这个await任务完成时,它下面的所有代码都会由单独的线程执行

下面是示例代码。执行它并检查线程 ID

using System;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncAwaitDemo
{
    class Program
    {
        public static async void AsynchronousOperation()
        {
            Console.WriteLine("Inside AsynchronousOperation Before AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
            //Task<int> _task = AsyncMethod();
            int count = await AsyncMethod();

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod Before Await, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            //int count = await _task;

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await Before DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            DependentMethod(count);

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await After DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
        }

        public static async Task<int> AsyncMethod()
        {
            Console.WriteLine("Inside AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
            int count = 0;

            await Task.Run(() =>
            {
                Console.WriteLine("Executing a long running task which takes 10 seconds to complete, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(20000);
                count = 10;
            });

            Console.WriteLine("Completed AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            return count;
        }       

        public static void DependentMethod(int count)
        {
            Console.WriteLine("Inside DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId + ". Total count is " + count);
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Started Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            AsynchronousOperation();

            Console.WriteLine("Completed Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            Console.ReadKey();
        }

    }
}
100赞 Blue Clouds 9/8/2018 #18

为了最快的学习..

  • 了解方法执行流程(附图):3 分钟

  • 问题内省(学习):1分钟

  • 快速掌握语法糖:5 分钟

  • 分享开发人员的困惑:5 分钟

  • 问题:快速将普通代码的实际实现更改为 异步代码:2 分钟

  • 下一步去哪里?

了解方法执行流程(附图):3 分钟

在这张图片中,只需关注#6(仅此而已)enter image description here

在 #6 步骤中,执行工作结束并停止。要继续,它需要来自 getStringTask(kind of a function) 的结果。因此,它使用运算符来暂停其进度,并将控制权(yield)交还给调用者(我们所处的这种方法)。对 getStringTask 的实际调用是在 #2 的前面进行的。在 #2 处,承诺返回字符串结果。但是它什么时候会返回结果呢?我们是否应该(#1:AccessTheWebAsync)再次进行第二次调用?谁得到结果,#2(调用语句)或#6(等待语句)await

AccessTheWebAsync() 的外部调用方现在也在等待。因此,调用方正在等待 AccessTheWebAsync,而 AccessTheWebAsync 目前正在等待 GetStringAsync。有趣的是,AccessTheWebAsync在等待之前做了一些工作(#2),也许是为了节省等待的时间。外部调用者(以及链中的所有调用者)也可以使用相同的多任务自由,这是这个“异步”的最大优点!你觉得它是同步的..或正常,但事实并非如此。

#2 和 #6 是分开的,所以我们有 #4(边等待边工作)的优势。但我们也可以在不拆分的情况下做到这一点。所以 #2 将是: .在这里,我们看不到任何优势,但是在链中的某个地方,一个函数将分裂,而其余函数则在不分裂的情况下调用它。这取决于您使用链中的哪个函数/类。这种从函数到函数的行为变化是本主题中最令人困惑的部分string urlContents = await client.GetStringAsync("...");

请记住,该方法已经返回(#2),它不能再次返回(没有第二次)。那么来电者怎么知道呢?这一切都与任务有关任务已返回。正在等待任务状态(不是方法,不是值)。值将在任务中设置。任务状态将设置为完成。调用方只监视 Task(#6)。所以 6# 是从哪里/谁得到结果的答案。稍后请进一步阅读 此处.

为学习而进行问题内省:1分钟

让我们稍微调整一下问题:

如何以及何时使用异步等待 Tasks

因为学习会自动涵盖其他两个(并回答您的问题)。Task

整个想法非常简单。一个方法可以返回任何数据类型(double、int、object 等),但在这里我们只是否认这一点并强制返回 '' 对象!但是我们仍然需要返回的数据(void除外),对吧?这将在 '' 对象内的标准属性中设置,例如: '' 属性。然后我们装箱/拆箱(转换为我们选择的数据类型)。TaskTaskResult

快速掌握语法糖:5 分钟

  • 原始非异步方法
internal static int Method(int arg0, int arg1)
        {
            int result = arg0 + arg1;
            IO(); // Do some long running IO.
            return result;
        }
  • 一个全新的 Task-ified 方法来调用上述方法
internal static Task<int> MethodTask(int arg0, int arg1)
    {
        Task<int> task = new Task<int>(() => Method(arg0, arg1));
        task.Start(); // Hot task (started task) should always be returned.
        return task;
    }

我们是否提到过 await 或 async?不。调用上述方法,您将获得一个可以监视的任务。您已经知道任务返回(或包含)的内容。整数。

  • 调用任务有点棘手,这是关键字开始出现的时候。如果有一个方法调用原始方法(非异步),那么我们需要按如下所示对其进行编辑。让我们调用 MethodTask()
internal static async Task<int> MethodAsync(int arg0, int arg1)
    {
        int result = await HelperMethods.MethodTask(arg0, arg1);
        return result;
    }

上面添加的相同代码如下图所示:enter image description here

  1. 我们正在“等待”任务完成。因此(强制语法)await
  2. 既然我们使用 await,我们就必须使用 (强制语法)async
  3. MethodAsync with 作为前缀(编码标准)Async

await很容易理解,但剩下的两个(,)可能不:)。好吧,不过,这对编译器来说应该更有意义。稍后的进一步阅读 这里asyncAsync

所以有 2 个部分。

  1. 创建“任务”(只有一个任务,它将是一个额外的方法)

  2. 创建用于调用任务的语法糖(如果要转换非异步方法,则涉及更改现有代码)await+async

请记住,我们有一个外部调用者访问 AccessTheWebAsync(),该调用者也未能幸免......也就是说,它也需要相同的。并且链仍在继续(因此这是一个可能会影响许多类的重大更改)。它也可以被视为非中断性更改,因为原始方法仍然存在。如果要强加重大更改,请更改其访问权限(或将其删除并将其移动到任务中),然后类将被迫使用 Task-method。无论如何,在异步调用中,一端总是有一个,而且只有一个。await+asyncTask

一切都很好,但一位开发人员惊讶地发现 Task 丢失了......

分享开发人员的困惑:5 分钟

开发人员犯了一个错误,没有实现,但它仍然有效!试着理解这个问题,只理解这里提供的公认的答案。希望您已经阅读并完全理解。总而言之,我们可能看不到/实现“Task”,但它是在父类/关联类中的某个地方实现的。同样,在我们的示例中,调用已经构建的方法比使用 () 我们自己实现该方法要容易得多。大多数开发人员发现在将代码转换为异步代码时很难掌握。TaskMethodAsync()TaskMethodTask()Tasks

提示:尝试找到一个现有的异步实现(如或)来外包难度。因此,我们只需要处理 Async 和 await(这很简单,与普通代码非常相似)MethodAsyncToListAsync

问题:快速将普通代码的实际实现更改为 异步操作:2 分钟

数据层中如下所示的代码行开始中断(许多地方)。因为我们将一些代码从 .Net framework 4.2.* 更新为 .Net core。我们必须在整个应用程序中在 1 小时内解决这个问题!

var myContract = query.Where(c => c.ContractID == _contractID).First();

轻松!

  1. 我们安装了 EntityFramework nuget 包,因为它具有 QueryableExtensions。或者换句话说,它执行异步实现(任务),因此我们可以在简单的代码中生存。Asyncawait
  2. 命名空间 = Microsoft.EntityFrameworkCore

调用代码行像这样更改

var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
  1. 方法签名从

Contract GetContract(int contractnumber)

async Task<Contract> GetContractAsync(int contractnumber)

  1. 调用方法也受到影响:被调用为GetContract(123456);GetContractAsync(123456).Result;

等!什么?好渔获! 只返回一个不是我们想要的值()。一旦操作的结果可用,它就会被存储起来,并在后续调用 Result 属性时立即返回。 我们也可以使用类似的 'Wait()' 进行超时实现ResultGetContractAsyncTaskContract

时间跨度 ts = TimeSpan.FromMilliseconds(150);

if (! t.Wait(ts)) Console.WriteLine(“经过的超时间隔。

  1. 我们在 30 分钟内到处都改变了它!

但是架构师告诉我们不要仅仅为此使用 EntityFramework 库!哎呀!戏剧!然后我们做了一个自定义的 Task 实现(yuk!)。你知道怎么做。还是很容易的! ..还是尤克..

下一步去哪里?我们可以观看一个关于在 ASP.Net Core 中将同步调用转换为异步的精彩快速视频,也许这可能是阅读本文后的方向。还是我解释得够多了?;)

评论

1赞 immitev 4/7/2020
不错的答案。您可能只想修复一些小问题,例如:(a) 提到“.Net framework 4.2”(据我所知,不存在这样的版本) (b) EntityFrameWork => EntityFramework 中的大小写
1赞 Philip Stratford 10/6/2020
无论我读了多少书,异步编程对我来说都是一个盲点,但这很有帮助。看到与最终分离使它变得有意义。你看到的几乎每个例子都显示了类似的东西,我只是从来不明白这有什么意义(也许没有!Task<string> getStringTask = client.GetStringAsync("...");string urlContents = await getStringTask;string urlContents = await client.GetStringAsync("...");
1赞 Blue Clouds 10/6/2020
我能想到@PhilipStratford原因是当父类调用它时(例如:调用 AccessTheWebAsync),并且该父类不需要多任务,而是被迫使用它。但是在AccessTheWebAsync内部,多任务处理是理想的,因此使用任务进行拆分(也是异步实现的原因)。
17赞 Hakim 9/28/2018 #19

异步/等待

实际上,Async / Await 是一对关键字,它们只是用于创建异步任务回调的语法糖。

以此操作为例:

public static void DoSomeWork()
{
    var task = Task.Run(() =>
    {
        // [RUNS ON WORKER THREAD]

        // IS NOT bubbling up due to the different threads
        throw new Exception();
        Thread.Sleep(2000);

        return "Hello";
    });

    // This is the callback
    task.ContinueWith((t) => {
        // -> Exception is swallowed silently
        Console.WriteLine("Completed");

        // [RUNS ON WORKER THREAD]
    });
}

上面的代码有几个缺点。错误不会传递,也很难阅读。 但是 Async 和 Await 来帮助我们:

public async static void DoSomeWork()
{
    var result = await Task.Run(() =>
    {
        // [RUNS ON WORKER THREAD]

        // IS bubbling up
        throw new Exception();
        Thread.Sleep(2000);

        return "Hello";
    });

    // every thing below is a callback 
    // (including the calling methods)

    Console.WriteLine("Completed");
}

Await 调用必须采用异步方法。这有一些优点:

  • 返回任务的结果
  • 自动创建回调
  • 检查错误并让它们在 Callstack 中冒泡(在 Callstack 中最多只能进行 none-await 调用)
  • 等待结果
  • 释放主线程
  • 在主线程上运行回调
  • 将线程池中的工作线程用于任务
  • 使代码易于阅读
  • 以及更多

注意:Async 和 Await 异步调用一起使用,进行这些调用。为此,您必须使用 Task Libary,例如 Task.Run() 。

以下是 await 和 none await 解决方案之间的比较

这是无异步解决方案:

public static long DoTask()
{
    stopWatch.Reset();
    stopWatch.Start();

    // [RUNS ON MAIN THREAD]
    var task = Task.Run(() => {
        Thread.Sleep(2000);
        // [RUNS ON WORKER THREAD]
    });
    // goes directly further
    // WITHOUT waiting until the task is finished

    // [RUNS ON MAIN THREAD]

    stopWatch.Stop();
    // 50 milliseconds
    return stopWatch.ElapsedMilliseconds;
}

这是异步方法:

public async static Task<long> DoAwaitTask()
{
    stopWatch.Reset();
    stopWatch.Start();

    // [RUNS ON MAIN THREAD]

    await Task.Run(() => {
        Thread.Sleep(2000);
        // [RUNS ON WORKER THREAD]
    });
    // Waits until task is finished

    // [RUNS ON MAIN THREAD]

    stopWatch.Stop();
    // 2050 milliseconds
    return stopWatch.ElapsedMilliseconds;
}

您实际上可以在不使用 await 关键字的情况下调用异步方法,但这意味着此处的任何异常都会在发布模式下被吞噬:

public static Stopwatch stopWatch { get; } = new Stopwatch();

static void Main(string[] args)
{
    Console.WriteLine("DoAwaitTask: " + DoAwaitTask().Result + " ms");
    // 2050 (2000 more because of the await)
    Console.WriteLine("DoTask: " + DoTask() + " ms");
    // 50
    Console.ReadKey();
}

Async 和 Await 不适用于并行计算。它们用于不阻塞您的主线程。当涉及到 asp.net 或 Windows 应用程序时,由于网络调用而阻塞主线程是一件坏事。如果这样做,您的应用程序将无响应甚至崩溃。

查看 MS 文档以获取更多示例。

32赞 James Mallon 1/25/2019 #20

Async & Await 简单解释

简单的类比

一个人可能会等待早上的火车。这就是他们正在做的一切,因为这是他们目前正在执行的主要任务。(同步编程(你通常做的!

另一个人可能会在抽烟然后喝咖啡时等待早上的火车。(异步编程)

什么是异步编程?

异步编程是指程序员选择在与执行主线程不同的线程上运行他的一些代码,然后在完成时通知主线程。

async 关键字实际上有什么作用?

将 async 关键字作为方法名称的前缀,例如

async void DoSomething(){ . . .

允许程序员在调用异步任务时使用 await 关键字。这就是它所做的一切。

为什么这很重要?

在许多软件系统中,主线程被保留用于专门与用户界面相关的操作。如果我在计算机上运行一个非常复杂的递归算法,需要 5 秒才能完成,但我在主线程(UI 线程)上运行它,当用户尝试单击我的应用程序上的任何内容时,它似乎被冻结,因为我的主线程已经排队并且当前正在处理太多操作。因此,主线程无法处理鼠标单击以通过单击按钮来运行该方法。

何时使用 Async 和 Await?

理想情况下,当您执行任何不涉及用户界面的事情时,请使用异步关键字。

因此,假设您正在编写一个程序,允许用户在手机上绘制草图,但每隔 5 秒,它就会在互联网上查看天气。

我们应该等待每 5 秒向网络调用一次轮询电话以获取天气,因为应用程序的用户需要不断与移动触摸屏交互才能绘制漂亮的图片。

如何使用 Async 和 Await

继上面的例子之后,这里有一些关于如何编写它的伪代码:

    //ASYNCHRONOUS
    //this is called using the await keyword every 5 seconds from a polling timer or something.

    async Task CheckWeather()
    {
        var weather = await GetWeather();
        //do something with the weather now you have it
    }

    async Task<WeatherResult> GetWeather()
    {

        var weatherJson = await CallToNetworkAddressToGetWeather();
        return deserializeJson<weatherJson>(weatherJson);
    }

    //SYNCHRONOUS
    //This method is called whenever the screen is pressed
    void ScreenPressed()
    {
        DrawSketchOnScreen();
    }

附注事项 - Update

我忘了在我原来的笔记中提到,在 C# 中,你只能等待包装在 Tasks 中的方法。例如,您可以等待此方法:

// awaiting this will return a string.
// calling this without await (synchronously) will result in a Task<string> object.
async Task<string> FetchHelloWorld() {..

您不能等待不是此类任务的方法:

async string FetchHelloWorld() {..

请在此处查看 Task 类的源代码。

评论

8赞 Prashant Abdare 11/27/2019
感谢您抽出宝贵时间写这篇文章。
1赞 coder kemp 6/24/2020
这是一个很有见地的现实世界类比!有时,对于一些开发人员来说,当进行实时类比时,理解技术部分变得容易得多。谢谢你
0赞 15ee8f99-57ff-4f92-890c-b56153 11/14/2020
这个答案既不简单,也不解释。
0赞 James Mallon 11/19/2020
感谢您的批评。人们以不同的方式摄取信息和学习,我怎样才能改进我的答案?有什么难以理解的?@15ee8f99-57FF-4F92-890C-B56153
0赞 repeatdomiau 2/10/2021
Asynchronous programming is where a programmer will choose to run some of his code on a separate thread from the main thread of execution and then notify the main thread on it's completion.这部分可以使用一些重构,异步不是并行的,可以在单线程语言/框架中使用。它真正做的是释放线程(甚至是主线程),当它等待处理器以外的任何工作(如磁盘、数据库、api 调用等)完成一些广泛的工作时......当它发出结果时,某个线程(相同或另一个)会恢复程序的处理。
11赞 repeatdomiau 2/10/2021 #21

我想为此付出两分钱,如果任何其他答案包含我将要解释的内容,我很抱歉,我阅读了大部分内容但没有找到它,但我可能会错过一些东西。

我看到了很多误解和很多很好的解释,只是想解释异步与并行编程的区别,我相信这将使事情更容易理解。

当您需要进行长时间的计算、处理器密集型工作时,您应该选择使用并行编程(如果可能)来优化内核的使用。这将打开一些线程并同时处理事情。

假设你有一个数字数组,并且想对每一个数字进行一些昂贵的长期计算。平行是你的朋友。

异步编程用于不同的用例。

当您等待不依赖于处理器的东西时,它用于释放您的线程,例如 IO(写入和读取磁盘),当您执行 IO 时,您的线程不执行任何操作,当您等待从数据库返回的昂贵查询的某些结果时也是如此。

异步方法在线程等待长时间返回结果时释放线程。此线程可由应用程序的其他部分使用(例如,在 Web 应用中,它处理其他请求),也可以返回到操作系统以供其他用途。

结果完成后,同一线程(或另一个线程)将返回给应用程序以继续处理。

在像 .net 这样的多线程环境中,异步编程不是强制性的(但是一种很好的做法),在 Web 应用中,其他线程将响应新请求,但如果你在像 nodejs 这样的单线程框架中,这是强制性的,因为你不能阻止你唯一的线程,否则你将无法响应任何其他请求。

总而言之,长时间的处理器密集型计算将从并行编程中受益更多,而不依赖于处理器的漫长等待期(如 IO 或数据库查询或对某些 API 的调用)将从异步编程中受益更多。

这就是为什么 Entity Framework 有一个异步 API 来保存、列出、查找等......

请记住,async/await 与 wait 或 waitAll 不同,上下文不同。Async/await 释放线程,是异步编程。wait / waitAll 阻止所有线程(它们不会被释放)以在并行上下文中强制同步......不同的东西...

希望这对某人有用......

-1赞 Raj Rj 2/11/2021 #22

异步与函数一起使用,使其成为异步函数。await 关键字用于同步调用异步函数。await 关键字保留 JS 引擎的执行,直到解析 promise 为止。

只有当我们想要立即获得结果时,我们才应该使用 async & await。也许从函数返回的结果正在下一行中使用。

关注这个博客,它用简单的单词写得很好

5赞 Madhusudhan V Indian 3/12/2021 #23

最好的例子就在这里,享受:

namespace ConsoleTestApp
{
    class Program
    {        
        static async Task Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Test1Async(3000);
            Test1Async(2000);
            Console.WriteLine("next statement");
            Console.ReadLine();
        }

        public static async Task Test1Async(int t)
        {
            Console.WriteLine("delaying " + t);
            await Task.Delay(t);
            Console.WriteLine("delay " + t + " completed");
        }

    }
}

enter image description here

评论

0赞 Theodor Zoulias 3/12/2021
嗨,Madhusudhan。请注意,在本站点中,通常不赞成将代码作为图像发布。
2赞 Vinit Divekar 8/18/2021
我不会称它为“最佳”。你在这里所拥有的是即发即弃的方法,不推荐使用。如果 Test1Async 中存在异常,则不会在 Main 方法中捕获这些异常。
1赞 Alex from Jitbit 3/15/2021 #24

回答你的第二个问题 - 何时使用 - 这是我们使用的一种相当简单的方法:async

  • 运行时间超过 50 毫秒的长时间运行的 I/O 绑定任务 - 使用 .async
  • 长时间运行的 CPU 密集型任务 - 使用并行执行、线程等。

解释:当您进行 I/O 工作时 - 发送网络请求、从磁盘读取数据等 - 实际工作是由“外部”硅(网卡、磁盘控制器等)完成的。工作完成后 - I/O 设备驱动程序将“ping”回操作系统,操作系统将执行您的延续代码、回调/等。在那之前,CPU 可以自由地做自己的工作(作为奖励,您还可以释放线程池线程,这对于 Web 应用程序的可扩展性来说是一个非常好的奖励)

P.S. 50ms 阈值是 MS 的建议。否则,(创建状态机、执行上下文等)增加的开销会吞噬所有好处。现在找不到原始的MS文章,但这里也提到了 https://www.red-gate.com/simple-talk/dotnet/net-framework/the-overhead-of-asyncawait-in-net-4-5/async

-4赞 user1095108 4/10/2022 #25

也许我的见解是相关的。 告诉编译器特殊处理一个函数,该函数是可挂起/可恢复的,它以某种方式保存状态。 暂停一项职能,但也是一种执行纪律的方式,是限制性的;你需要指定你在等待什么,你不能无缘无故地暂停,这就是使代码更具可读性,也许也更有效率的原因。这就引出了另一个问题。为什么不做多件事,为什么一次只做一件?我相信这是因为这种模式已经建立起来,程序员们遵循最小惊讶的原则。存在模棱两可的可能性:你是只满足其中一个条件,还是希望所有条件都得到满足,也许只是其中的一部分?asyncawaitawait

0赞 simi 3/6/2023 #26

Async 只是让我们能够使用 await。如果我们不在函数中使用 await,它什么都不做。 使用 await 时,我们需要在附近写一个 awaitable 的名称,最常见的是 Task 或 Task。 await 使任务的操作一直运行,直到完成。只有当它完成时,才会执行函数的下一个代码。