【问题标题】:Is using async/await better then using task.Start() and why?使用 async/await 是否比使用 task.Start() 更好,为什么?
【发布时间】:2013-11-17 21:21:56
【问题描述】:

比较以下两种方法:

static async Task<int> DownloadAsync(string url)
{
    var client = new WebClient();
    var awaitable = client.DownloadDataTaskAsync(url);
    byte[] data = await awaitable;
    return data.Length;
}

用法:Task&lt;int&gt; task = DownloadAsync("http://stackoverflow.com");

static Task<int> Download(string url)
{
    var client = new WebClient();
    var task = client.DownloadDataTaskAsync(url);
    byte[] data = task.Result;
    return Task.FromResult(data.Length);
}

用法:

Task task = new Task(() => Download("http://stackoverflow.com"));
task.Start();

据我所知,这两种方法都是异步运行的。我的问题是:
这两种方法的行为有什么不同吗?
为什么我们更喜欢 async-await 而不是它是一个很好的模式?

【问题讨论】:

  • 确保在不需要捕获SynchronizationContext 的方法中使用.ConfigureAwait(false),您在DownloadAsync 中的所有等待都可以使用它。请参阅here 了解原因。

标签: c# task async-await


【解决方案1】:

你发布的两种方法完全不同。

DownloadAsync 是一个真正的异步方法。这意味着在下载数据时,该异步操作上没有线程阻塞。

Download 通过调用Task.Result 同步阻塞调用线程。我在我的博客why Result should not be used with asynchronous Tasks上解释过:一般情况下,会导致死锁。但是让我们假设没有死锁。然后,您从 TPL 任务中调用它,因此它会阻塞任务线程(很可能是线程池线程)。在下载数据时,该任务线程在该异步操作上被阻塞。

所以,DownloadAsync 效率更高。

【讨论】:

  • byte[] data = task.Result; 在工作线程上被调用,所以我假设只有这个工作线程会被阻止,这对应于byte[] data = await awaitable; 阻止那个工作线程 - 这不是正确的吗?我相信在您的文章中,结果是在 UI 线程中调用的,因此会阻塞?
  • DownloadAsync 案例中没有工作线程。 Result 的死锁问题是 deadlock 问题,而不是 blocking 问题。如果您还没有这样做,我建议您阅读我的intro to async 帖子。
  • 阅读和理解是两个不同的东西 - helas ;)
  • DownloadAsync 怎么可能是“真正的异步方法”又是“没有工作线程”?我的意思是 await 部分显然是在工作线程上执行的,完成后在该工作线程上的方法中返回(通常)。
  • @Gerard:不,“等待部分”不在工作线程上执行。在较高级别(稍微简化),发生的情况是这样的:DownloadDataTaskAsync 只是向 TCP/IP 堆栈注册一个回调。所以没有线程 只是坐在那里等待那个数据包。当数据包到达时(从网卡的中断开始),DownloadDataTaskAsync 完成它的Task,这反过来又安排DownloadData 的其余部分执行。关键是从发送请求到收到回复的这段时间内,没有线程。没有线程池线程,没有操作系统线程,什么都没有
【解决方案2】:

new Task 将使用TaskScheduler.Current 执行整个方法,通常这会使用ThreadPool

通过使用 async/await,该方法是同步进入的,并且仅在需要时才使用异步延续。

我的意思可以用下面的 LINQPad 程序来证明:

const int delay = 1;

public async Task DoSomethingAsync()
{
    Thread.CurrentThread.ManagedThreadId.Dump();
    await Task.Delay(delay).ConfigureAwait(false);
    Thread.CurrentThread.ManagedThreadId.Dump();
}

void Main()
{
    DoSomethingAsync().Wait();  
}

尝试将delay改为0,你会看到continuation在同一个线程上恢复,这是因为Task.Delay在没有延迟的情况下会立即返回,这样就避免了安排和执行continuation时的开销不是必需的。

通过使用new Task,您将失去这个聪明的功能并始终使用ThreadPool 线程,即使异步方法的实现者可能认为没有必要。

【讨论】:

  • 您的回答有点棘手,但您指出了这两种方法的真正区别。
  • @Gerard Yeh,async/await 解释起来有点棘手,尤其是在凌晨 1.15 点,我可能会在早上尝试改进它。
  • 没有 async 的方法完全在工作线程中运行,有 async-await 的方法使用调用线程直到 await 所在的位置,然后在等待之后它在工作线程中运行,除了在您的延迟(0)示例中 - 当没有真正的任务时 - 它在调用线程中再次运行。
  • @Gerard 你完全明白了。想象一下您的DownloadAsync 更改为开始缓存结果,第二次调用将能够立即返回。如果您使用new Task,您将使用线程池线程来返回缓存值。使用 async/await 调用者不需要担心那些实现细节,它只是说,当你准备好时给我结果。
  • new Task() 不执行任何操作。而Task.Start() 使用TaskScheduler.Current 执行它。这通常与TaskScheduler.Default 相同,但并非总是如此。
【解决方案3】:

看看这个post,Stephen Cleary 准确解释了原因和差异。

简而言之,它完全一样。这是新的与旧的等待方式。我发现 async / await 也更好看,因为您将在另一种方法中拥有额外的代码,并且不需要放置 task.Start()。

【讨论】:

    猜你喜欢
    • 2012-03-20
    • 1970-01-01
    • 2020-05-03
    • 2015-06-30
    • 2011-06-30
    • 1970-01-01
    • 2012-08-27
    • 1970-01-01
    相关资源
    最近更新 更多