【问题标题】:Calling asynchronous function synchronously is more responsive同步调用异步函数响应更快
【发布时间】:2021-10-15 14:58:48
【问题描述】:

我有一个这样的异步方法:

private async Task TaskAsync()
{
    await Task.Run(() => Task.Delay(2000));
}

然后我在一个按钮单击事件中调用它,我已经声明如下:

private async void button1_Click(object sender, EventArgs e)
{
    await TaskAsync();

    MessageBox.Show("Afterwards.");
}

现在,当我单击按钮时,TaskAsync() 确实处于等待状态,并且直到 TaskAsync() 完成执行后才会显示消息框。但是,当我在点击事件中调用TaskAsync()时删除await命令,然后执行立即跳转到消息框。

我在这里做错了吗?这是 async...await 的正常行为吗?

我的项目是一个 .NET Core 5 C# winform 项目。

【问题讨论】:

  • 有两个问题。首先,TaskAsync 方法使用Task.Run 运行另一个 异步方法。那只是浪费一个线程。它应该只是await Task.Delay(2000);。其次,如果您不等待任务执行将立即进行。这就是使用await 的全部意义——等待已经执行的异步任务完成

标签: winforms asynchronous async-await event-handling .net-5


【解决方案1】:

这里有两个问题。首先,TaskAsync 方法使用Task.Run 运行另一个异步方法。那只是浪费一个线程。它应该只是:

private async Task TaskAsync()
{
    await Task.Delay(2000);
}

如果没有

private Task TaskAsync()=>Task.Delay(2000);

其次,如果没有等待任务执行将立即进行。这就是使用await 的全部意义——等待已经在执行的异步任务完成而不阻塞调用线程。

原代码相当于:

private async void button1_Click(object sender, EventArgs e)
{
    await Task.Delay(2000);

    MessageBox.Show("Afterwards.");
}

没有awaitTask.Delay()返回的任务会被忽略,消息框会立即显示。

如果要开始一个长操作,例如读取一个大文件,同时显示一条消息,可以将任务存储在一个字段中并等待对话框关闭后

private async void button1_Click(object sender, EventArgs e)
{
    var task=File.ReadAllTextAsync(...);
    MessageBox.Show("Reading a file");
    var text=await task;
    MessageBox.Show("Afterwards.");
}

private async void button1_Click(object sender, EventArgs e)
{
    var task=Task.Run(()=>SomeBackgroundProcessing(someArgs));
    MessageBox.Show("Processing");
    var text=await task;
    MessageBox.Show("Afterwards.");
}

【讨论】:

  • 谢谢你,@PanagiotisKanavos。是的,我现在已经意识到 async-await 是如何工作的。我只是习惯于使用线程并立即执行下一条语句;我将不得不习惯这一点。此外,await Task.Run(() => Task.Delay(2000)); 的原因是它是真实代码的快速缩减版本。 :)
  • @ocm 在这种情况下,您应该发布 real 代码。很多 API 都有异步替代方案,尤其是与 IO 相关的方法(例如 HttpClient.GetAsync,WinForms 中 EF 的 ToListAsync(). Blocking with async` 可以用.ConfigureAwait(false) 解决。CPU 密集型通常需要Task.Run方法的一部分。
  • @ocm 与线程并发当您必须同时处理超过 2 或 3 个操作时很难 - 此外 IO 不需要线程。这就是为什么很多语言都采用promise、futures甚至async/await关键字来隐藏这种复杂性,例如JavaScript、Kotlin、C++、Rust。
  • 嗨@PanagiotisKanavos,TaskAsync() 的真正用途并不重要,我的问题是需要下一条语句(在我的示例中显示一个消息框)同时执行。我不同意 IO 不需要线程。线程只是处理并发的一种方式,由用户决定使用它们还是异步等待。
  • @ocm 我不是在谈论代码如何调用 IO 操作。 IO 意味着你总是在等待其他东西来响应,所以不需要一个活动线程来做任何事情。 CPU 只是等待。在 Windows 上,操作系统本身总是使用异步 IO 和回调而不是阻塞线程。模拟阻塞操作。当您读取文件或进行 HTTP 调用时,操作系统将启动操作并立即返回。当该操作完成时,它将调用一个回调方法。
猜你喜欢
  • 2012-07-25
  • 2013-07-12
  • 2020-10-13
  • 2012-02-25
  • 2020-04-20
  • 1970-01-01
  • 2020-12-08
  • 2016-08-09
  • 2015-03-19
相关资源
最近更新 更多