你是否必须将Task.Run放在一个方法中才能使它成为异步?

时间:2013-06-15 00:28:23

标签: c# .net-4.5 async-await c#-5.0

我试图以最简单的形式理解异步等待。我想创建一个非常简单的方法,为了这个例子添加两个数字,被授予,它根本没有处理时间,这只是在这里制定一个例子。

示例1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

示例2

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

如果我等待DoWork1Async(),代码会同步还是异步运行?

我是否需要使用Task.Run包装同步代码以使方法等待和异步,以免阻塞UI线程?

我正在尝试弄清楚我的方法是Task还是返回Task<T>我是否需要使用Task.Run包装代码以使其异步。

愚蠢的问题我很确定,但我在网上看到人们正在等待代码中没有任何异步内容并且未包含在Task.RunStartNew中的示例。

3 个答案:

答案 0 :(得分:528)

首先,让我们清楚一些术语:“异步”(async)意味着它可以在启动之前将控制权交还给调用线程。在async方法中,这些“收益”点是await表达式。

这与术语“异步”非常不同,因为MSDN文档多年来使用(mis)表示“在后台线程上执行”。

为了进一步混淆这个问题,async与“等待”非常不同;有一些async方法的返回类型是不可接受的,许多方法返回不是async的等待类型。

足够他们不是;这是他们

  • async关键字允许异步方法(即允许await表达式)。 async方法可能会返回TaskTask<T>或(如果必须)void
  • 任何遵循某种模式的类型都是可以等待的。最常见的等待类型是TaskTask<T>

因此,如果我们将您的问题重新表述为“如何以可接受的方式在后台线程上运行”,那么答案就是使用Task.Run

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

(但这种模式很差;见下文)。

但如果您的问题是“如何创建可以回复其调用者而非阻止”的async方法,则答案是声明方法async并使用{{1}为它的“屈服”点:

await

所以,事情的基本模式是让private async Task<int> GetWebPageHtmlSizeAsync() { var client = new HttpClient(); var html = await client.GetAsync("http://www.example.com/"); return html.Length; } 代码依赖于async表达式中的“awaitables”。这些“等待”可以是其他await方法,也可以是返回等待的常规方法。返回async / Task 的常规方法可以使用Task<T>在后​​台线程上执行代码,或者(更常见的)他们可以使用Task.Run或其中一个快捷方式(TaskCompletionSource<T>TaskFactory.FromAsync等)。我建议在Task.FromResult中包装整个方法;同步方法应该具有同步签名,并且应该由消费者决定它是否应该包含在Task.Run中:

Task.Run

我的博客上有async/await intro;最后是一些很好的后续资源。 private int DoWork() { return 1 + 2; } private void MoreSynchronousProcessing() { // Execute it directly (synchronously), since we are also a synchronous method. var result = DoWork(); ... } private async Task DoVariousThingsFromTheUIThreadAsync() { // I have a bunch of async work to do, and I am executed on the UI thread. var result = await Task.Run(() => DoWork()); ... } 的MSDN文档也非常好。

答案 1 :(得分:21)

使用 async 装饰方法时最重要的一点是,至少有一个 在方法中等待 运算符。在您的示例中,我将使用TaskCompletionSource将其翻译为如下所示。

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async void DoWork()
{
    int result = await DoWorkAsync();
}

答案 2 :(得分:11)

当您使用Task.Run运行方法时,Task从线程池中获取一个线程来运行该方法。所以从UI线程的角度来看,它是“异步的”,因为它不会阻止UI线程。这对于桌面应用程序来说很好,因为你通常不需要很多线程来处理用户交互。

但是,对于Web应用程序,每个请求都由线程池线程提供服务,因此可以通过保存此类线程来增加活动请求的数量。经常使用线程池线程来模拟异步操作对于Web应用程序是不可扩展的。

True Async不一定涉及使用线程进行I / O操作,例如文件/数据库访问等。您可以阅读此内容以了解I / O操作不需要线程的原因。 http://blog.stephencleary.com/2013/11/there-is-no-thread.html

在您的简单示例中,它是一个纯CPU绑定计算,因此使用Task.Run很好。