如何以及何时使用'async'和'await'

Dan*_*inu 989 .net c# asynchronous async-await

从我的理解主要事情之一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;
}
Run Code Online (Sandbox Code Playgroud)

Dan*_*inu 723

使用时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;
}
Run Code Online (Sandbox Code Playgroud)

好的,所以这里发生了什么:

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

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

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

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

  • 这个答案显然是错误的.这些许多赞成会对许多用户造成错误的理解.MS文档明确指出,只使用async时没有使用其他线程,等待.https://msdn.microsoft.com/en-us/library/mt674882.aspx请有人纠正答案.因此,我浪费了一整天. (99认同)
  • 如果每个异步方法都需要在其中进行等待,并且await只能在具有异步的方法上完成,那么什么时候停止? (62认同)
  • 为什么我们有"等待""Task.Delay(1000);" 在LongRunningOperation异步方法? (61认同)
  • @BenisonSam线程有点旧,但我有同样的问题,一直在寻找答案."await"的原因是如果我们省略"await",LongRunningOperationAsync()将立即返回.实际上,如果我们删除await,编译器将发出警告.Stephen Cleary的博客文章http://blog.stephencleary.com/2011/09/async-ctp-why-do-keywords-work-that-way.html给出了优秀的设计讨论. (11认同)
  • @codea在Eric Lippert对文章[他将一篇介绍性文章](http://msdn.microsoft.com/en-us/magazine/hh456401.aspx)链接到这个主题的评论中,他特意将DoEvents策略与async-await进行了比较 (3认同)
  • 任何线程都无法处理UI问题,而在许多示例中,我们可以在等待之后访问代码中的UI,您的解释中有错误 (2认同)
  • 我以前是一名 VB6 程序员,await 让我想起了“Do Events”指令,我用它来告诉 UI 在执行密集循环时刷新。我这样想对吗? (2认同)
  • 啊!可怕的 DoEvents() 方法!http://blog.codinghorror.com/is-doevents-evil/ (2认同)
  • @BenisonSam因为`Task.Delay()`实现`async`所以它可以与`await`关键字异步运行. (2认同)
  • @JohnB,`Task.Delay()`不是实现`async`修饰符(比如`public static Task Delay(int millisecondsDelay);`)但是我理解所有在输出类型中定义了`GetAwaiter`的方法都被认为是作为异步方法,也是等待的.例如`Task`类有一个方法`public TaskAwaiter GetAwaiter();`.这意味着所有具有`Task`返回类型的方法都是等待的. (2认同)
  • Task.Delay() 是异步运行的可等待方法。async 方法需要 await 方法,但 await 方法可以是 async 或 Task 方法。 (2认同)
  • @KrishnaDeepak使用异步方法时,该方法同步执行,直到等待.然后在等待之后,该方法异步执行.有时您必须使用Task.Run显式运行新线程并等待它.该方法首先同步运行,然后异步运行. (2认同)
  • 我认为在这些示例中使用“await Task.Delay”更令人困惑而不是有帮助。它应该模拟一个长时间运行的同步操作,但由于它是可等待的,这会增加混乱,因为该方法现在必须添加“async”修饰符。显示长时间运行的 do/loop 会更有意义。 (2认同)
  • @KrishnaDeepak 斯蒂芬·克利里叔叔的想法被大多数人记住了,而没有思考那篇文章,因此从你的评论中可以明显看出它。**操作系统级别**可能没有**额外的线程,但**内核**有额外的线程。否则,您认为如何处理中断(例如通过 I/O 设备),尤其是在尖端智能设备上?只是一种**开发人员**评论,而不是**工程师**。我们知道可能不需要额外的线程,就像 Javascript 处理回调一样。然而,我根本不知道你试图通过分享一个**简单的**链接来确定什么点。 (2认同)

Ste*_*ary 157

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

它们是为了使异步代码易于编写和读取,是的.

是否与产生后台线程执行长持续时间逻辑相同?

一点也不.

//我不明白为什么必须将此方法标记为"异步".

async关键字使await关键字.因此await必须标记任何使用方法async.

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

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

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

没有.


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

  • @Stanislav:我有[博客条目](http://blog.stephencleary.com/2011/09/async-ctp-why-do-keywords-work-that-way.html)来解决这个问题. (7认同)
  • 所以它不是在后台线程上运行,但它也不会阻塞.这是可能的,因为异步API使用回调而不是用线程来处理.您启动(I/O,套接字,...)操作并返回执行操作.操作完成后,操作系统将调用回调.这就是Node.js或Python Twisted框架所做的,他们也有一些很好的解释. (4认同)
  • "async关键字启用了await关键字.所以使用await的任何方法都必须标记为异步.", - 但为什么呢?这个答案无助于理解为什么必须将该方法标记为异步.编译器不能通过查看await关键字来推断该方法是异步的吗? (3认同)
  • @PeterLarsen'CPH':我的答案是“异步与生成线程不同”,“默认情况下,异步方法不会在另一个线程上运行”,以及“异步方法中的`Sleep`不在另一个线程上运行”。所有这些都是正确的并且文档也同意。 (3认同)
  • 建议澄清:不,因为默认情况下`async`方法不会在另一个线程上运行.***在你的例子中,`DoSomethingAsync()`中的`Sleep()`调用阻止当前线程阻止执行在`button1_Click()`中继续,直到`DoSomethingAsync()`完成.请注意,虽然`Thread.Sleep()`阻止正在执行的线程,但`Task.Delay()不会.*** (2认同)

Joe*_*ips 149

说明

以下是高级别的异步/等待的快速示例.除此之外还有更多细节需要考虑.

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

在下面的示例中,第一个块正是这样做的.它立即启动所有任务(Task.Delay线条)并将它们放在一边.代码将await a在行上暂停,直到完成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);
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

有关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);
Run Code Online (Sandbox Code Playgroud)

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

await a.ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)

该计划完成被暂停后,它将继续有可能在一个完全不同的线程具有不同的背景.这是性能改进的来源 - 它可以继续在任何可用的线程上,而不必恢复它开始的原始上下文.

这个东西令人困惑吗?地狱啊!你能搞清楚吗?大概!一旦你掌握了这些概念,然后转向Stephen Cleary的解释,这些解释往往更倾向于对已经异步/等待有技术理解的人.

  • @veerendragupta是的.在这种情况下,您会有意识地选择不以异步方式运行它们(因为它们不是异步的).关于配置上下文还有一些其他的事情需要实现,我不会在这里讨论 (3认同)
  • @Jocie不太好.当你调用`await`时,我认为它会将线程释放回池而不是持有它.这使得它可以在等待返回任务时在其他地方使用 (2认同)
  • @JoePhillips我想你刚才说的是async/await的本质.调用线程被释放,可以被机器上的其他进程使用.等待调用完成后,将使用新线程恢复调用者最初启动的内容.调用者仍在等待,但好处是在此期间释放了一个线程.那是异步/等待的好处吗? (2认同)

Adr*_*der 147

进一步回答其他问题,看看等待(C#参考)

更具体地说,在所包含的例子中,它解释了你的情况

以下Windows窗体示例说明了在异步方法WaitAsynchronouslyAsync中使用await.将该方法的行为与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";
}
Run Code Online (Sandbox Code Playgroud)

  • 我确实相信,从**节await表达式不会阻止它正在执行的线程.相反,它会导致编译器将其余的异步方法注册为等待任务的延续.然后,Control返回到异步方法的调用者.当任务完成时,它会调用它的继续,异步方法的执行将在它停止的地方恢复.** (30认同)
  • 根据[此MSDN文章](http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx#BKMK_WhentoUseAsynchrony),"async和await关键字不会导致创建其他线程.. ..异步方法不能在自己的线程上运行".我的理解是,在等待关键字时,框架会向前跳过(返回调用者)以允许所有可能的独立代码在等待长操作完成时运行.我认为这意味着一旦所有独立代码都运行,如果长操作没有返回,它将阻塞.不过,我现在正在学习这个. (11认同)
  • @astander这是不正确的.它*不*在不同的线程上执行.它只是安排在`Task.Delay`使用的计时器触发时调用的继续(方法的其余部分). (8认同)
  • 感谢您的回答。但是,是否在单独的线程上执行WaitAsynchronouslyAsync()? (3认同)
  • 由于睡眠,这个答案是错误的。使用 await Task.Delay(1000); 查看已接受的答案;它具有正确的行为。 (2认同)

spp*_*c42 55

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

Starting Long Running method...
Press any key to exit...
End Long Running method...
Run Code Online (Sandbox Code Playgroud)

从而,

  1. Main通过TestAsyncAwaitMethods启动长时间运行的方法.这会立即返回而不会暂停当前线程,我们会立即看到"按任意键退出"消息
  2. 所有这些,LongRunningMethod在后台运行.一旦完成,来自Threadpool的另一个线程将获取此上下文并显示最终消息

因此,不会阻止线程.


Vnu*_*nuk 39

我认为你已经选择了一个糟糕的例子 System.Threading.Thread.Sleep

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

System.Threading.Thread.Sleep 它不是"正在完成"的东西,它只是睡觉,因此你的下一行是在5秒后达到的......

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

  • @Abdurrahim `Thread.Sleep` 会阻塞线程(线程除了闲置之外不能做任何其他事情),但异步方法不会。在“DownloadFileAsync”的情况下,线程可以执行其他操作,直到来自远程服务器的回复。异步方法中“某些需要时间的任务”的更好占位符是“Task.Delay”,因为这实际上是异步的。 (3认同)
  • 为什么睡眠是不好的例子,而下载是很好的例子。当我看到Thread.Sleep时,就像FooBar之类的东西,我知道有些任务需要花费时间。我认为他的问题很重要 (2认同)
  • @GabrielLuci 我的反对意见不是关于延迟与睡眠;而是关于延迟与睡眠。你的答案看起来更像是一个稻草人的答案;如果你把它作为对这个问题的评论,我不会反对,但作为一个答案,它闻起来更像是一个稻草人的答案。我认为在那里使用异步仍然可以,即使他/她必须进行的所有调用都将是阻塞调用;它不会使所有的目的无效......即使剩下的所有内容都将是语法糖,它被视为有效的情况, (2认同)
  • 这不是我的答案。但要解决你的观点:这取决于该方法的目的。如果他只是想要一个方法来调用,他就成功了。但在本例中,他试图创建一个异步运行的方法。他仅使用“async”关键字就做到了这一点。但他的方法仍然同步运行,这个答案完美地解释了原因:因为他实际上没有运行任何异步代码。标记为“async”的方法仍然同步运行,直到您“等待”不完整的“任务”。如果没有“await”,则该方法将同步运行,编译器会对此发出警告。 (2认同)

Mar*_*lls 19

这是一个快速的控制台程序,让跟踪者清楚."TaskToDo"方法是您想要进行异步的长时间运行方法.使其运行Async由TestAsync方法完成.测试循环方法只运行"TaskToDo"任务并运行它们Async.您可以在结果中看到它们,因为它们在运行之间没有以相同的顺序完成 - 它们在完成时向控制台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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Lex*_* Li 12

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

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

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

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


Ton*_*oda 12

这里的所有答案都使用Task.Delay()或其他一些内置的异步函数.但这是我的例子,不使用这些异步函数:

    // Starts counting to a large numbewr 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;
    }
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!第一个实际起作用而不是等待的答案。 (2认同)
  • @encoder task.wait() 和 async/await 不是同一个概念,要小心。一种适用于并行编程线程同步,另一种适用于异步编程线程释放。他们是相反的。wait() 阻塞,await 避免阻塞...问题是 C# 使用 Task 来表示它们...所以你可能会使用错误的... (2认同)

atl*_*ste 9

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

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

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

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


Hak*_*kim 9

异步/等待

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

以该操作为例:

    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]
        });
    }
Run Code Online (Sandbox Code Playgroud)

上面的代码有几个缺点。错误不会传递,很难读取。但是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");

    }
Run Code Online (Sandbox Code Playgroud)

等待调用必须在Async方法中。这具有一些优点:

  • 返回任务的结果
  • 自动创建回调
  • 检查错误并让它们在调用堆栈中冒泡(仅在调用堆栈中最多等待呼叫)
  • 等待结果
  • 释放主线程
  • 在主线程上运行回调
  • 使用线程池中的工作线程执行任务
  • 使代码易于阅读
  • 还有更多

注意:Async和Await 异步调用一起使用不要使用它们。为此,您必须使用Task Libary,例如Task.Run()。

这是等待和不等待解决方案之间的比较

这是非异步解决方案:

    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;
    }
Run Code Online (Sandbox Code Playgroud)

这是异步方法:

    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;
    }
Run Code Online (Sandbox Code Playgroud)

您实际上可以在没有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();
    }
Run Code Online (Sandbox Code Playgroud)

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

查阅ms docs以获取更多示例。


vib*_*006 8

请参阅此小提琴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;
    }   

}
Run Code Online (Sandbox Code Playgroud)

来自输出窗口的跟踪: 在此输入图像描述


Jam*_*lon 7

异步并等待简单说明

简单类比

一个人可能会等待早上的火车。这就是他们正在做的所有事情,因为这是他们当前正在执行的主要任务。(同步编程(您通常会做什么!))

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

什么是异步编程?

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

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

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

async void DoSomething(){ . . .
Run Code Online (Sandbox Code Playgroud)

允许程序员在调用异步任务时使用await关键字。这就是全部。

为什么这很重要?

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

什么时候使用异步和等待?

在执行不涉及用户界面的任何操作时,最好使用异步关键字。

因此,可以说您正在编写一个程序,该程序允许用户在手机上绘制草图,但是每5秒钟它将检查一次互联网上的天气。

由于应用程序的用户需要与移动触摸屏保持互动以绘制漂亮的图片,因此我们应该等待每5秒钟轮询一次的呼叫以获取天气信息。

您如何使用异步和等待

在上面的示例之后,这是一些如何编写它的伪代码:

     //ASYNCHRONOUS
    //this is called every 5 seconds
    async void 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();
    }
Run Code Online (Sandbox Code Playgroud)

  • 感谢您花时间写这篇文章。 (5认同)

rep*_*iau 6

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

我看到了很多误解和很多很好的解释,只是想从异步与并行编程的不同方面来解释它,我相信这会让事情更容易理解。

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

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

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

它用于在您等待不依赖于您的处理器的东西时释放您的线程,例如 IO(向/从磁盘写入和读取),您的线程在您执行 IO 时什么都不做,当您等待时同样的事情一些结果来自从数据库返回的昂贵查询。

当线程等待很长时间返回结果时,异步方法会释放它。这个线程可以被你的应用程序的其他部分使用(例如,在一个 web 应用程序中它处理其他请求)或者可以返回到操作系统用于其他用途。

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

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

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

这就是为什么实体框架,例如,有一个异步 api 来保存、列出、查找等......

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

希望这对某人有用...


小智 5

我理解的方式也是,应该在组合中添加第三个术语:Task.

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

Taskasync函数的返回值。它异步执行。

await一个任务。当代码执行到达这一行时,控制跳回到周围原始函数的调用者。

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


ABa*_*pai 5

在更高的层面上:

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

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

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

4)如果主线程在一个仍在执行的任务上看到await,它不会等待它并返回到当前方法的调用者。通过这种方式,应用程序保持响应。

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

    }
}
Run Code Online (Sandbox Code Playgroud)


Blu*_*uds 5

为了最快的学习。

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

  • 问题自省(学习上的缘故):1分钟

  • 快速了解语法糖:5分钟

  • 分享开发者的困惑:5分钟

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

  • 下一步要去哪里?

了解方法执行流程(带有图表):3分钟

在此图像中,仅关注#6 在此处输入图片说明

在#6步骤:AccessTheWebAsync()已经用尽了,而没有getStringTask的结果。因此,AccessTheWebAsync使用await运算符来暂停其进度并将控制权交还给调用者。AccessTheWebAsync将Task(具有字符串返回值)返回给调用方。该任务表示产生字符串结果的承诺。但是什么时候可以打回电话呢?再次打个电话?

AccessTheWebAsync()的调用方除了等待外什么都没有做(它可以完成一些内部任务,然后在需要时等待)。因此,调用方正在等待AccessTheWebAsync,而AccessTheWebAsync目前正在等待GetStringAsync。

请记住,该方法已经返回,无法再次返回(没有第二次)。那么呼叫者怎么知道?一切都与任务有关任务已返回。等待任务(不是方法,不是值)。值将在“任务”中设置。任务状态将设置为完成。呼叫方仅监视任务。进一步的阅读在这里稍后。

为了学习而进行的问题自省:1分钟

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

如何以及何时使用and asyncawait Tasks

因为学习会Task自动覆盖其他2。至少是为了学习。当然,这是对有关async和的问题的解答await

快速了解语法糖:5分钟

  • 转换前(原始方法)

    internal static int Method(int arg0, int arg1) { int result = arg0 + arg1; IO(); // Do some long running IO. return result; }

  • 另一种Task定义的方法可以调用上述方法

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

我们提到了等待还是异步?否。调用上述方法,您将获得一个任务。您可以监视。您已经知道任务返回什么。整数。

  • 调用任务有些棘手。让我们调用MethodTask()

    internal static async Task<int> MethodAsync(int arg0, int arg1) { int result = await HelperMethods.MethodTask(arg0, arg1); return result; }

我们正在“等待”完成任务。因此await。由于我们使用await,因此必须使用async(mandatory)和MethodAsync,并将“ Async”作为前缀(编码标准)。进一步阅读以供稍后在这里使用

分享开发者的困惑:5分钟

开发人员犯了一个错误,即不执行,Task但仍然可以使用!尝试理解问题,并仅提供此处提供的可接受的答案。希望您已经阅读并完全理解。同样,在我们的示例中,调用一个已构建MethodAsync()的方法比使用我们自己的TaskMethodTask())方法更容易。大多数开发人员发现,很难Tasks将代码转换为异步代码。

提示:尝试查找现有的Async实现(例如MethodAsyncToListAsync)以将困难外包。因此,我们只需要处理Async和await(这很简单,与普通代码非常相似)

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

下面在数据层中显示的代码行开始中断(很多地方)。因为我们将某些代码从.Net Framework 4.2更新为.Net core。我们必须在整个应用程序中在1小时内解决此问题!

var myContract = query.Where(c => c.ContractID == _contractID).First();
Run Code Online (Sandbox Code Playgroud)

十分简单!

  1. EntityFrameWork nuget(具有QueryableExtensions)
  2. 命名空间= Microsoft.EntityFrameworkCore

这样的代码被更改

var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
Run Code Online (Sandbox Code Playgroud)
  1. 方法签名从更改为

    Contract GetContract(int contractnumber)

    async Task<Contract> GetContractAsync(int contractnumber)

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

  3. 我们在30分钟内到处更改了它!

但是架构师告诉我们不要为此使用EntityFrameWork库!哎呀!戏剧!然后,我们做了一个自定义的Task实现。你知道的。还是容易!

下一步要去哪里? 我们可以观看一段精彩的快速视频,介绍如何在ASP.Net Core中将同步调用转换为异步,因为这很可能是阅读此书后的方向。

  • 很棒的答案!这对我帮助很大 (2认同)

小智 5

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

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

    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

  • 我不会称其为“最佳”。这里采用的是即发即忘的方法,不推荐。如果 Test1Async 中出现异常,则不会在 Main 方法中捕获异常。 (3认同)

Ale*_*lex 5

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

  1. 长时间运行的非 CPU任务(磁盘 I/O、网络、GPU、蓝牙、USB 等等)- 使用async.
  2. 长时间运行的 CPU 密集型任务 - 使用并行执行、线程等。

说明:当您执行“Type-1”工作时 - 发送网络请求、从磁盘读取数据或执行 GPU 计算等 - 实际工作是由“外部”芯片(网卡、磁盘控制器、nVidia 芯片、蓝牙控制器、USB 等)。一旦工作完成,外部设备的驱动程序将“ping”回操作系统,操作系统将执行您的继续代码或回调。在此之前,CPU 可以自由地完成自己的工作(作为奖励,您还可以释放线程池线程,这对于 Web 应用程序的可扩展性来说是一个很好的奖励)