在异步方法

时间:2017-12-04 20:55:56

标签: c# .net async-await

有这样的应用:

    static void Main(string[] args)
    {
        HandleRequests(10).Wait();
        HandleRequests(50).Wait();
        HandleRequests(100).Wait();
        HandleRequests(1000).Wait();

        Console.ReadKey();
    }

    private static async Task IoBoundWork()
    {
        await Task.Delay(100);
    }

    private static void CpuBoundWork()
    {
        Thread.Sleep(100);
    }

    private static async Task HandleRequest()
    {
        CpuBoundWork();
        await IoBoundWork();
    }

    private static async Task HandleRequests(int numberOfRequests)
    {
        var sw = Stopwatch.StartNew();

        var tasks = new List<Task>();

        for (int i = 0; i < numberOfRequests; i++)
        {
            tasks.Add(HandleRequest());
        }

        await Task.WhenAll(tasks.ToArray());

        sw.Stop();

        Console.WriteLine(sw.Elapsed);
    }

在此应用的输出下方:

enter image description here

从我的观点来看,在一种方法中具有CPU绑定和IO绑定的部分是非常常见的情况,例如解析/归档/序列化某些对象并将其保存到磁盘,因此应该可以正常工作。但是在上面的实现中,它的工作速度很慢。你能帮我理解为什么吗?

如果我们在Task中包装CpuBoundWork()的主体,它会显着提高性能:

    private static async Task CpuBoundWork()
    {
        await Task.Run(() => Thread.Sleep(100));
    }

    private static async Task HandleRequest()
    {
        await CpuBoundWork();
        await IoBoundWork();
    }

enter image description here

为什么没有Task.Run它会如此缓慢?为什么我们可以在添加Task.Run后看到性能提升?我们是否应该总是在类似的方法中使用这种方法?

3 个答案:

答案 0 :(得分:1)

for (int i = 0; i < numberOfRequests; i++)
    {
        tasks.Add(HandleRequest());
    }

返回的任务是在await的第一个HandleRequest()创建的。所以你在一个线程上执行所有CPU绑定代码:for循环线程。完全序列化,完全没有并行性。

当您将CPU部件包装在任务中时,您实际上将CPU部件作为任务提交,因此它们是并行执行的。

答案 1 :(得分:1)

你正在做的事情就是这样:

|-----------HandleRequest Timeline-----------|
|CpuBoundWork Timeline| |IoBoundWork Timeline|

尝试这样做:

private static async Task HandleRequest()
{
    await IoBoundWork();
    CpuBoundWork();
}

它具有启动IO工作的优势,在等待时,CpuBoundWork()可以进行处理。您只需等待您需要回复的最后一刻。

时间表看起来有点像这样:

|--HandleRequest Timeline--|
|Io...
   |CpuBoundWork Timeline|
      ...BoundWork Timeline|

另外,在Web环境中谨慎打开额外线程(Task.Run),每个请求已经有一个线程,因此将它们相乘会对可伸缩性产生负面影响。

答案 2 :(得分:0)

您已经指出您的方法应该是异步的,方法是让它返回Task,但您实际上并没有(完全)异步。您对该方法的实现会执行一系列昂贵,长时间运行,同步工作,然后然后返回给调用者,并以异步方式执行其他一些工作。

然而,该方法的调用者认为它实际异步(完整)并且它不会同步执行昂贵的工作。他们假设他们可以多次调用该方法,让它立即返回,然后继续,但由于你的实现没有立即返回,而是在返回之前做了一堆昂贵的工作,该代码不能正常工作(具体来说,它不能在前一个操作返回之前启动下一个操作,因此同步工作不是并行完成的。)

请注意您的&#34;修复&#34;不是很惯用的。您正在使用异步过同步反模式。而不是使CpuBoundWork async并使其返回Task,尽管是一个CPU绑定操作,它应该保持为HandleRequest应该处理的指示CPU绑定的工作应该通过调用Task.Run

在另一个线程中异步完成
private static async Task HandleRequest()
{
    await Task.Run(() => CpuBoundWork());
    await IoBoundWork();
}