多个等待操作或只有一个

时间:2016-04-17 10:47:54

标签: c# asp.net-mvc asynchronous async-await

我已经了解了await关键字是如何实现的,以及它创建的结果结构。我想我对它有一个基本的了解。但是,是

public async Task DoWork()
{
    await this.Operation1Async();
    await this.Operation2Async();
    await this.Operation3Async();
}

"更好" (一般来说)或

public async Task DoWork()
{
    await this.Operation1Async();
    this.Operation2();
    this.Operation3();
}

第一种方法的问题是它为每个等待调用创建一个新的Task?这需要一个新线程?

首先在第一个Task上创建一个新的await,然后在新的Task处理所有内容?

修改 好吧也许我不太清楚,但如果我们有

while (await reader.ReadAsync())
{
    //...
}

await reader.NextResultAsync();

// ...

这不是创造两个任务吗?一个在主线程中,第一个ReadAsync,然后在这个新创建的任务中使用NextResultAsync的另一个任务。我的问题是第二项任务真的需要,不是 在主线程中创建的一个任务是否足够?所以

while (await reader.ReadAsync())
{
    //...
}

reader.NextResult();

// ...

5 个答案:

答案 0 :(得分:5)

  

它是为每个等待呼叫创建一个新任务吗?这需要一个新线程?

是和否。是的,它为每个异步方法创建一个Task; async状态机将创建一个。但是,这些任务不是线程,也不是在线程上运行。他们不会“跑”到任何地方。

你可能会发现我的一些博客文章很有用:

  • async intro,其中解释了async / await的工作原理。
  • There Is No Thread,解释了如何在没有线程的情况下完成任务。
  • Intro to the Task type,它解释了某些任务(委托任务)如何在线程上运行代码并运行,但async(Promise Tasks)使用的任务却没有。
  

第一个在第一个await上创建一个新任务,然后在新任务中处理那里的所有内容?

完全没有。任务只完成一次,并且该方法不会继续超过await,直到该任务完成。因此,Operation1Async返回的任务在Operation2被调用之前已经完成。

答案 1 :(得分:2)

这两个示例在功能上并不相同,因此您可以根据具体需要选择一个示例。在第一个示例中,顺序执行3个任务,而在第二个示例中,第二个和第三个任务并行运行,而不等待其结果完成。同样在第二个示例中,DoWork方法可以在第二个和第三个任务完成之前返回。

如果您想在离开DoWork方法主体之前确保完成任务,则可能需要执行此操作:

public async Task DoWork()
{
    await this.Operation1Async();
    this.Operation2().GetAwaiter().GetResult();
    this.Operation3().GetAwaiter().GetResult();
}

当然这绝对是非常糟糕的,你永远不应该这样做,因为它阻止了主线程,在这种情况下你会使用第一个例子。如果这些任务使用I / O完成端口,那么你一定要利用它们而不是阻塞主线程。

另一方面,如果你问你是否应该使Operation2Operation3异步,那么答案是这样的:如果他们正在做I / O绑定的东西你可以利用我/ O完成端口然后你应该绝对让它们异步并采用第一种方法。如果它们是CPU绑定操作,你不能使用IOCP,那么最好让它们保持同步,因为在你要阻止的单独任务中执行这个CPU绑定操作是没有任何意义的。

答案 2 :(得分:1)

  

第一种方法的问题是它是为每个等待调用创建一个新任务吗?这需要一个新线程?

这是您的误解,导致您对第一个示例中的代码产生怀疑。

Task不需要新线程。如果你想这样做,Task当然可以在新线程上运行,但任务的一个重要用途是当任务直接或间接地通过异步i / o工作时,这发生在任务或者任务时反过来等待,使用异步i / o访问文件或网络流(例如Web或数据库访问),允许池化的线程返回池,直到i / o完成。

因此,如果任务没有立即完成(例如,如果其目的可以完全从当前填充的缓冲区完成,则可能发生),当前运行它的线程可以返回到池中,并且可以用于执行其他操作。与此同时。当i / o完成后,来自池的另一个线程可以接管并完成等待,然后可以等待等待的任务,等等。

因此,您的问题中的第一个示例允许总共使用更少的线程,尤其是当其他工作也将使用来自同一池的线程时。

在第二个示例中,第一个await完成后,处理完成的线程将阻塞同步等效方法。如果其他操作也需要使用池中的线程,那么该线程没有被返回给它,新的线程将不得不旋转。因此,第二个示例是需要更多线程的示例。

答案 3 :(得分:0)

一个并不比另一个好,他们会做不同的事情。

在第一个示例中,每个操作都在一个线程上进行调度和执行,由Task表示。注意:它不能保证它们发生在哪个线程上。

await关键字表示(松散地)“等待此异步操作完成然后继续”。继续,不一定在同一个线程上完成。

这意味着示例一,是异步操作的同步处理。现在只是因为创建了Task,它不会推断Thread也被创建,TaskScheduler使用的线程池已经创建,非常小的开销实际上已经介绍了。

在第二个示例中,await将使用调度程序调用第一个操作,然后正常调用接下来的两个 。没有为后两次调用创建TaskThread,也不会在Task上调用方法。

在第一个示例中,您还可以考虑同时进行异步调用。这将调度所有三个操作“同时”运行(不保证),并等待它们全部执行完毕。

public async Task DoWork()
{
    var o1 = this.Operation1Async();
    var o2 = this.Operation2Async();
    var o3 = this.Operation3Async();

    await Task.WhenAll(o1, o2, o3);
}

答案 4 :(得分:0)

这取决于您的任务以及执行任务的方式,即使对于IO操作也是如此。

通常,使用多次等待会导致创建更少的线程和更优化。但是,在某些情况下,使用多次等待会严重破坏您的性能。

这里只是一个夸张的例子,当然没有人会读取这样的文件,但是请想象一下,如果您从网络中读取数据包!

    static void Main(string[] args)
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        
        var stream = File.OpenRead(@"bigfile");

        stopwatch.Restart();
        Console.WriteLine("One task with many sync operations");
        Task.Run(()=> ReadAllSync(stream)).Wait();
        Console.WriteLine($"time: {stopwatch.ElapsedMilliseconds}\n");
        stream.Position = 0;

        Console.WriteLine("Multiple sync/await operations");
        ReadAllAsync(stream).Wait();
        Console.WriteLine($"time: {stopwatch.ElapsedMilliseconds}\n");
    }

    static long ReadAllSync(Stream stream)
    {
        long ret = 0;
        var buffer = new byte[1];
        for (var i = 0; i < 10000000; i++)
        {
            stream.Read(buffer, 0, buffer.Length);
            ret += buffer[0];
        }
        return ret;
    }

    static async Task<long> ReadAllAsync(Stream stream)
    {
        long ret = 0;
        var buffer = new byte[1];
        for (var i = 0; i < 10000000; i++)
        {
            await stream.ReadAsync(buffer, 0, buffer.Length);
            ret += buffer[0];
        }
        return ret;
    }

这是结果:

One task with many sync operations
time: 173

Multiple sync/await operations
time: 36747