1283

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

24 回答 24

886

使用时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到达。

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

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

于 2013-11-14T18:55:19.103 回答
207

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

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

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

一点也不。

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

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

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

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

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

不。


您可能会发现我的async/await介绍很有帮助。官方 MSDN 文档也异常出色(尤其是TAP部分),并且该团队async提出了出色的常见问题解答

于 2013-01-22T13:17:40.580 回答
199

解释

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

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

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

在下面的示例中,第二个块正在启动一个任务并等待它完成(即完成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),尽管我认为你可能会失去一些优化的机会。

这有一个方面使得async/await概念有点难以掌握。这就是在这个例子中,这一切都发生在同一个线程上的事实(或者至少就其而言似乎是同一个线程SynchronizationContext)。默认情况下,await将恢复运行它的原始线程的同步上下文。例如,在 ASP.NET 中,HttpContext当请求进入时,您有一个与线程相关联的内容。此上下文包含特定于原始 Http 请求的内容,例如具有语言、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已经有技术理解的人。

于 2017-05-26T14:55:26.933 回答
156

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

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

以下 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";
}
于 2013-01-22T09:39:00.187 回答
75

为了最快的学习..

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

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

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

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

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

  • 下一步去哪里?

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

在这张图片中,只关注#6(仅此而已) 在此处输入图像描述

在第 6 步,执行用完了工作并停止了。要继续,它需要来自 getStringTask(一种函数)的结果。因此,它使用一个await操作符来暂停它的进程,并将控制权交还给调用者(我们所在的这个方法)。对 getStringTask 的实际调用是在 #2 早些时候进行的。在 #2 处,承诺返回一个字符串结果。但是它什么时候会返回结果呢?我们(#1:AccessTheWebAsync)是否应该再次拨打第二个电话?谁得到结果,#2(调用语句)或#6(等待语句)

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

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

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

为学习而自省:1 分钟

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

如何以及何时使用and asyncawait Tasks

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

整个想法很简单。一个方法可以返回任何数据类型(double、int、object 等),但在这里我们只是否认这一点并强制Task返回一个 ' ' 对象!但是我们仍然需要返回数据(除了 void),对吧?这将在“ Task”对象内的标准属性中设置,例如:“ Result”属性。

快速了解语法糖:5 分钟

  • 原始的非异步方法
internal static int Method(int arg0, int arg1)
        {
            int result = arg0 + arg1;
            IO(); // Do some long running IO.
            return result;
        }
  • 一个全新的任务化方法来调用上述方法
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;
    }

上面添加的相同代码如下图所示: 在此处输入图像描述

  1. 我们正在“等待”任务完成。因此await(强制语法)
  2. 由于我们使用await,所以我们必须使用async(强制语法)
  3. MethodAsyncAsync作为前缀(编码标准)

await很容易理解,但剩下的两个 ( async, Async) 可能不是:)。好吧,它应该对编译器更有意义。稍后在此处进一步阅读

所以有2个部分。

  1. 创建“任务”(只有一个任务,这将是一种附加方法)

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

请记住,我们有一个 AccessTheWebAsync() 的外部调用者,并且该调用者也不能幸免……即它也需要相同的调用者await+async。并且链条还在继续(因此这是一个可能影响许多类的重大变化)。它也可以被认为是非破坏性更改,因为原始方法仍然可以调用。如果您想进行重大更改,请更改它的访问权限(或删除并将其移动到任务中),然后类将被迫使用任务方法。无论如何,在异步调用中总会有一个Task在一端并且只有一个。

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

分享开发者的困惑: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();

十分简单!

  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?接得好!GetContractAsync只返回一个Task不是我们想要的值(Contract)。一旦操作的结果可用,它将被存储并在后续调用该Result属性时立即返回。我们也可以使用类似的“Wait()”来实现超时

TimeSpan ts = TimeSpan.FromMilliseconds(150);

if (!t.Wait(ts​​)) Console.WriteLine("超时时间已过");

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

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

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

于 2018-09-08T06:29:55.820 回答
74

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

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. 一直以来,它LongRunningMethod都在后台运行。完成后,来自 Threadpool 的另一个线程会获取此上下文并显示最终消息

因此,没有线程被阻塞。

于 2015-08-13T09:32:13.183 回答
51

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

Task的重点async是让它在后台执行而不锁定主线程,例如做一个DownloadFileAsync

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

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

于 2013-01-22T09:32:54.840 回答
26

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

附加说明 - 更新

我忘了在我的原始笔记中提到,在 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 类的源代码。

于 2019-01-25T12:24:22.933 回答
25

这是一个快速控制台程序,可以让那些关注的人清楚。该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);
        }
    }
}
于 2015-07-17T15:36:15.120 回答
19

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

// 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;
}
于 2017-07-15T08:03:51.310 回答
15

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

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

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

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

于 2014-09-08T02:06:42.077 回答
14

异步/等待

实际上,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");
}

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

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

注意:Async 和 Await异步调用一起使用以进行这些调用。您必须为此使用任务库,例如 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 文档以获取更多示例。

于 2018-09-27T17:28:26.450 回答
11

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

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

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

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

于 2013-01-22T10:16:25.133 回答
10

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

}

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

于 2018-01-24T12:45:39.177 回答
7

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

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

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

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

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

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

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

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

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

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

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

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

希望这对某人有用...

于 2021-02-10T02:32:04.103 回答
5

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

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

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

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

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

于 2017-06-02T21:15:25.070 回答
5

在更高的层次上:

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

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

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

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

    }
}
于 2018-08-05T17:21:39.103 回答
4
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;
    }
}
于 2018-02-21T21:45:06.527 回答
4

最好的例子在这里,享受: 在此处输入图像描述

于 2021-03-12T08:34:36.973 回答
2

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

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

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

于 2016-10-04T16:16:30.753 回答
2

下面是通过打开对话框读取 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
    {
    }
}
于 2018-04-26T10:51:54.723 回答
0

这里的答案作为关于等待/异步的一般指导很有用。它们还包含有关等待/异步如何连接的一些细节。我想和你分享一些在使用这种设计模式之前你应该知道的实践经验。

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

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

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 站点还提供了有关此主题的更详细讨论的链接。

于 2018-04-10T08:03:42.437 回答
0

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

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

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

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

于 2021-03-15T12:40:11.757 回答
-1

async 与函数一起使用以使其成为异步函数。await 关键字用于同步调用异步函数。await 关键字持有 JS 引擎执行,直到 promise 被解决。

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

关注这个博客,用简单的文字写得很好

于 2021-02-11T13:39:05.690 回答