c# async wait 让我困惑,似乎什么也没做

rut*_*god 2 c# async-await

编辑:更新LongTask()为使其能够await Task.Delay(2000);按我需要的方式工作,所以感谢您的回答,你们!


我试图从同步函数中调用异步函数,以使同步函数在异步函数完成之前继续运行。我不知道为什么下面的代码要等待LongTask()完成才能继续。我认为只有我这样做了才应该这样做await LongTask()。有人可以帮助我理解我所缺少的吗?

C#在这里摆弄

using System;
using System.Diagnostics;
using System.Threading.Tasks;
                    
public class Program
{
    public static void Main()
    {
        var timer = new Stopwatch();
        timer.Start();
        LongTask();
        timer.Stop();
        
        long ms = timer.ElapsedMilliseconds;

        Console.WriteLine(ms.ToString());
    }
    
    async static void LongTask()
    {
        Task.Delay(2000).Wait();
    }
}
Run Code Online (Sandbox Code Playgroud)

Cai*_*ard 5

这是 async/await 的简短介绍

你知道,当你玩一些上帝类型的游戏时,你可以让你的定居者建造大量建筑物,或者砍伐树木/制作食物等,然后你除了坐下来看着他们完成之外没有其他事可做,然后你就可以开始再次,让建筑物大量生产骑士或弹射器或其他什么?

这有点浪费生命,你坐在那里除了等待某件事完成之外什么也不做,然后你可以继续执行其他任务。

作为一个人,你可能不会这样做;你去泡杯咖啡,打电话给朋友,打沙袋,甚至可能去玩太空探索游戏,设置任何一艘船的自动驾驶仪,引导你前往某个恒星系统,这将需要 5 分钟的等待和看着星星经过..自动驾驶仪设置,咖啡煮好了,建筑物还没有完工,厌倦了沙袋..所以你去清理..

这不是并行做事;而是并行做事。你不是一边搅拌咖啡一边打电话聊天,一边用布擦地板,但它充分利用了你的时间,把大量的精力投入到一件事情上,完成待办事项清单,尽可能地完成你能做的事情,然后切换到下一件事。你一次做一件事,但当你陷入困境时,你就会转向另一件事。如果你坐着等待一切,你需要 10 个副本才能完成 10 次停止/启动工作

async/await 是同一回事。它需要同步代码——从头到尾在一个长操作中完成的事情,这是线程注意力的唯一焦点,即使Wait()中间有 5 分钟的时间,它实际上在 5 分钟内什么都不做——而且将其分成一堆单独的方法,这些方法由一个进程控制,该进程允许线程从中断的地方继续执行

标记一个方法async是 C# 的一个指标,允许编译器无形地将方法分割成可以在需要时放下和拾取的片段

await是一个指示器,您在说“我已经达到了需要完成一些操作才能进一步进行此操作的程度;去寻找其他事情要做”。当它遇到一个时,await您的线程将停止对代码的工作,因为它被告知在后台操作完成之前不执行任何其他操作(正在执行的工作并不重要,让我们想象它是某个后台线程)并且您的线程将继续执行直到结果准备好为止。这确实简化了许多与编程相关的事情,因为您只处理一个线程 - 它的代码行为类似于同步代码,但在卡住时会执行其他操作,而不是不执行任何操作

由于关键字await必须后跟可等待的内容,通常是一个TaskTask<T>- 它代表已经设置为发生的操作。当操作完成并且线程返回时,await也有助于解开控制该过程的任务对象,并给出结果。

如果你没有从中学到任何其他东西,至少要学会:

  • 必须标记一个方法,就像您想在其中async使用一样。await通常,您应该将方法的返回类型设置sync为 Task(如果它不返回任何值)或Task<X>(如果它返回 X 类型的值)。您还应该将该方法命名为以..Async
  • 当您启动一些异步工作的操作时(您调用一些称为..Async的方法),并且您得到一个Task<X>对象作为回报,使用await它来获取X您真正想要的
  • 创建异步方法意味着调用它的方法也必须是异步的,并且调用该方法的方法也必须是异步的……并且……一直到树上并超出代码。这就是当你的线程必须去寻找其他事情要做时它可以“逃逸”的方式 - 它需要从你的代码中逃逸,因此你可以通过声明“一路异步”来实现这一点

我在评论中说过不要使用控制台应用程序。假设您有一个 Windows 窗体应用程序。Win Forms 应用程序通常只有一个线程可以完成所有工作。一个线程绘制 UI,当您单击一个按钮时,它就会运行您放入button_Click()处理程序中的所有代码。虽然它这样做,但它并没有完成绘制 UI 的正常工作。您可以将其设置statusLabel.Text = "Downloading data.."为该方法的第一行,然后您可以启动 1 gig 文件的下载,然后您可以将状态标签设置为"Finished " + downloadedFile.Length。单击该按钮后,您什么也看不到 - 下载文件时应用程序的 UI 卡住了 30 秒。然后它突然在标签中显示“已完成 1024000000”:

void button_Click(object sender, EventArgs e){
  statusLabel.Text = "Starting download";
  string downloadedFile = new SomeHttpDownloadStringThing("http://..");
  statusLabel.Text = "Finished " + downloadedFile.Length;
}
Run Code Online (Sandbox Code Playgroud)

为什么?嗯.. 绘制 UI 并在屏幕上绘制标签像素的线程非常忙于下载文件。它进入该SomeHttpDownloadStringThing方法并在 30 秒内没有出来,然后它出来,再次设置状态标签文本,然后它离开您的代码并返回到它通常居住的任何地方。它在屏幕上绘制了像素“完成的..”。它甚至不知道它必须绘制“开始下载”。在它再次拿起画笔之前,该数据就被覆盖了

让我们将其改为异步:

async void button_Click(object sender, EventArgs e){
  statusLabel.Text = "Starting download";
  string downloadedFile = await SomeHttpDownloadStringThingAsync("http://..");
  statusLabel.Text = "Finished " + downloadedFile.Length;
}
Run Code Online (Sandbox Code Playgroud)

SomeHttpDownloadStringThing返回了一个string. SomeHttpDownloadStringThingAsync返回了一个Task<string>. 当 UI 线程遇到该await想象时,它会告诉某个后台线程去下载文件,暂停此方法,然后从中返回并继续绘制 UI。它将标签绘制为“开始...”,以便您看到它,然后也许会找到其他一些事情要做。如果你有一个计时器来计时下载的时间,那么你的计时器也会很好地增加 - 因为 UI 线程可以自由地执行此操作,它不会等待 Windows 执行下载,而它除了等待之外什么都不做。

下载完成后,线程将被回调到原来的位置并从那里继续。它不会再次运行第一个代码,也不会再次将状态标签设置为“正在启动”。

它从字面上开始SomeHttpDownloadStringThingAsync,就好像它从未离开过一样,将其await转换Task<string>为下载文件的a string,然后UI线程可以设置“完成..”文本

整个事情就像同步版本一样,除了中间的一点点,允许 UI 线程返回到在屏幕上绘制像素并处理其他用户交互的常规工作

这就是为什么我说控制台应用程序很难理解异步/等待,因为在等待过程中它们的 UI 显然不会做任何其他事情,除非您已经安排了一些事情发生,但这是更多的工作。UI 应用程序会自动执行其他操作,如果您堵塞执行这些操作的线程,它们就会冻结,并变为“无响应”

如果您有敏锐的洞察力,async void即使我说过“如果您有一个 void 方法,当您使其异步时,请使其返回 Task”,您也会发现处理程序 - winforms 事件处理程序是一个特殊情况,因为考虑到,你不能制造它们async Task,但它们是例外而不是规则。目前,经验法则 - 避免async void