C#中的异步方法不是真正的异步?

6

我创建了下面的控制台应用程序,但它似乎是同步运行而不是异步运行,这让我感到有些困惑:

class Program
{
    static void Main(string[] args)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        var total = CreateMultipleTasks();
        stopwatch.Stop();

        Console.WriteLine("Total jobs done: {0} ms", total.Result);
        Console.WriteLine("Jobs done in: {0} ms", stopwatch.ElapsedMilliseconds);
    }

    static async Task<int> CreateMultipleTasks()
    {
        var task1 = WaitForMeAsync(5000);
        var task2 = WaitForMeAsync(3000);
        var task3 = WaitForMeAsync(4000);

        var val1 = await task1;
        var val2 = await task2;
        var val3 = await task3;

        return val1 + val2 + val3;

    }

    static Task<int> WaitForMeAsync(int ms)
    {
        Thread.Sleep(ms);
        return Task.FromResult(ms);
    }
}

运行应用程序时,输出结果如下:

总工作时间:12000毫秒
工作完成时间:12003毫秒

我本来期望的是:

总工作时间:12000毫秒
工作完成时间:5003毫秒

这是因为当我使用Thread.Sleep方法时,它停止了整个应用程序的进一步执行吗?或者我忽略了什么重要的东西吗?

2
你把异步和多任务混淆了,它们并不相同。 - MarcinJuraszek
正如@MarcinJuraszek所说,尝试切换Thread.Sleep到其他异步操作,例如WebClient.DownloadStringTaskAsync()。同时等待所有线程,而不是一个接一个地等待。 async并非多任务。 - flindeberg
3个回答

5
即使你按照其他答案的建议使用 Task.RunTask.Delayasync 方法中应尽量避免使用阻塞的 Task.WaitAll 。混合使用异步和同步代码通常是不好的想法,它增加了冗余阻塞线程的数量并促进死锁。

相反,使用 await Task.WhenAll 并将阻塞等待移到顶层(即,在此示例中为 Main 方法):

class Program
{
    static void Main(string[] args)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        var total = CreateMultipleTasks();

        total.Wait();

        stopwatch.Stop();

        Console.WriteLine("Total jobs done: {0} ms", total.Result);
        Console.WriteLine("Jobs done in: {0} ms", stopwatch.ElapsedMilliseconds);
    }

    static async Task<int> CreateMultipleTasks()
    {
        var task1 = Task.Run(() => WaitForMeAsync(5000));
        var task2 = Task.Run(() => WaitForMeAsync(3000));
        var task3 = Task.Run(() => WaitForMeAsync(4000));

        await Task.WhenAll(new Task[] { task1, task2, task3 });

        return task1.Result + task2.Result + task3.Result;
    }

    static int WaitForMeAsync(int ms)
    {
        // assume Thread.Sleep is a placeholder for a CPU-bound work item
        Thread.Sleep(ms);
        return ms;
    }
}

顺便提一下,查看Stephen Toub的“我是否应该为同步方法公开异步包装器?”“我是否应该为同步方法公开异步包装器?”


2
您可以同步运行任务。您可以这样做:
static async Task<int> CreateMultipleTasks()
{
    var task1 = Task.Run<int>(() => WaitForMeAsync(5000));
    var task2 = Task.Run<int>(() => WaitForMeAsync(3000));
    var task3 = Task.Run<int>(() => WaitForMeAsync(4000));

    Task.WaitAll(new Task[] { task1, task2, task3 });

    return task1.Result + task2.Result + taks3.Result;

}

连续使用三个await不会并行运行任务。它只会在等待时释放线程(如果您使用await Task.Delay(ms),因为Thread.Sleep(ms)是一个阻塞操作),但当前执行不会在task1“休眠”时继续执行task2


@ssg:不,如果你使用await,它们将始终一个接一个地运行,因为这是await的目的... - Christoph Fink
1
@ssg 好的,经过几次编辑、多次评论和15分钟的努力,你的回答终于指出了一条正确的方向。干得好! - MarcinJuraszek
@ssg eeee,你使用了我评论中提到的完全相同的东西(多任务处理)。我不明白为什么你感到满意,因为你并没有证明我是错的 :) 但无论如何,今晚的离题讨论就到此为止吧。 - MarcinJuraszek
@chrfin 不,它不使用多线程。我说过,“Task.Run使用线程而不是异步执行”。而Marcin明确表示在任何其他方式下都不可能实现,这意味着他将术语“多任务处理”专门用于“线程”。异步可以让您同时执行任务,但与多线程无关。我的答案在单个线程中运行,但同时执行。 - Sedat Kapanoglu
如果您可以使用多线程(即使用多个操作系统线程来分割一个任务),那么与异步执行(使用单个线程同时执行多个操作)无关,这一点需要注意。@olf - Sedat Kapanoglu
显示剩余15条评论

1

你的WaitForMeAsync方法只是一个假装是异步方法的简单同步方法。

你没有执行任何异步操作,Sleep()只会阻塞线程。

为什么要延迟异步?(是的,你可以使用await Task.Delay(ms)),需要更多的输入来帮助解决问题。


我打算将一个现有的应用程序中一些相当重的 SQL 调用更改为异步调用。因此,在开始更改真正的应用程序之前,我只想确保我的异步设置是正确的。 - olf
你的应用程序是ASP.NET应用程序还是控制台应用程序?或者问题是你是否试图降低应用程序(控制台或ASP.NET)的延迟,或使其处理更多的请求每秒(ASP.NET)。 - Yishai Galatzer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接