异步函数以严格顺序的方式执行

时间:2017-10-11 02:36:28

标签: c# asynchronous async-await

我试图在C#中围绕async await。我写过这个有两个文件的小型Windows控制台应用程序。

Downloader.cs

namespace AsyncAwait
{
    public class Downloader
    {
        public async Task DownloadFilesAsync()
        {
            // In the Real World, we would actually do something...
            // For this example, we're just going to print file 0, file 1.
            await DownloadFile0();
            await DownloadFile1();
        }
        public async Task DownloadFile0()
        {
            int count = 0;

            while (count < 100)
            {            
               Console.WriteLine("Downloading File {0,1} ----> {1,3}%",0,count);
               Thread.Sleep(10);
               count++;
        }
        await Task.Delay(100);
        }

        public async Task DownloadFile1()
        {
           int count = 0;            
           while (count < 100)
           {                
              Console.WriteLine("Downloading File {0,1} ----> {1,3}%", 1, count);
              Thread.Sleep(10);
              count++;
           }
           await Task.Delay(100);
        }
    }
}

Program.cs

namespace AsyncAwait
{
    class Program
    {
        static void Main(string[] args)
        {
              Downloader d = new Downloader();
              d.DownloadFilesAsync().Wait();
              Console.WriteLine("finished");   
        }
    }
}

在输出中,我一个接一个地看到文件downloadedfile1后跟file2)。我在DownloadFile0以及DownloadFile1的while循环中都睡了。

我希望它们像一个不同的线程一样发生,而不是以严格的顺序方式。根据我的理解,这是async的全部内容?我的理解错了吗?如果现在我该如何解决这个问题?

2 个答案:

答案 0 :(得分:3)

在您的DownloadFilesAsync中 你需要这样做:

 public async Task DownloadFilesAsync()
    {
        // In the Real World, we would actually do something...
        // For this example, we're just going to print file 0, file 1.
        var task1 = DownloadFile0();
        var task2 = DownloadFile1();
        await Task.WhenAll(task1, task2).
    }

基本上,等待意味着您在进入下一行之前等待执行任务。因此,让任务执行,然后在最后使用WhenAll等待。 另外,尝试阅读有关ConfigureAwait(false)的信息,它将使您免于死锁:)

更新 另外,将Thread.Sleep更改为等待Task.Delay(100); (模拟文件下载,并使用实际的异步函数进行文件下载) Thread.Sleep意味着你的主线程一旦到达这一行就会休眠并且它不会产生,所以你的任务将在最终等待这一行之前经过整个循环:await Task.Delay(100);并执行去DownloadFile1。 另一方面,Task.Delay的产生和执行将转到DownloadFile1,直到它到达第一个等待。

答案 1 :(得分:3)

让我试着解释一下你在做什么,希望你会看到为什么事情会顺序发生。

想象一下你,是的,你是主线程并开始执行main功能。您创建了一个新的Downloader,然后您会看到DownloadFilesAsync()方法,因此您可以使用它。你转到那个方法,然后你看到DownloadFile0()所以你去那个方法。你开始做循环,你在每次循环迭代中睡眠10毫秒。循环完成后,您可以执行某些操作。这是重要的部分:在此您决定不想执行Delay。您要求您希望线程池中的一个线程执行100毫秒的延迟。因为你把单词await,你也说:“嘿,请求线程池中的一些线程等待100毫秒,完成后告诉我。在此之后不要做任何事情。请告诉我。 “然后,由于线程池线程将等待100毫秒,您将立即返回到行await DownloadFile0();。在这里你还有一个await所以你不再进一步了。您立即返回main方法。接下来,您要做的就是,d.DownloadFilesAsync()已完成.Wait();。所以你等着别什么也不做。

过了一段时间,100毫秒之后,你被打断了:“嘿主线程,你要求其中一个池线程等待100毫秒。它已经完成了。你可以从这里拿走它。”所以你回到你离开的地方:await Task.Delay(100); DownloadFile0()。你继续,除了返回之外,在这一行之后无事可做。因此,在此行之后您返回await DownloadFile0();并且自DownloadFile0()完成后,您将移至下一行await DownloadFile1();

因此,DownloadFile1()仅在DownloadFile0()完成所有操作后才会启动。然后你在DownloadFile1()中做了同样的事情。你,主要的,线程做了所有的工作。您要求线程池线程执行的唯一工作是Task.Delay(100);,它在每个方法中等待100毫秒。在那个时候你是自由的,但除了等待你别无所求。

因此,所有的工作都是顺序的。

注意:即使Task.Delay(100)按顺序完成也是可能的。调度程序将调用IsCompleted方法,如果完成,则不会异步完成。

如果您正在进行真正的异步工作,例如调用Web服务来下载大型文件或其他IO工作,那么当您发生这种情况时,主线程,而不是等待可能已经显示了一个很好的进度条用户声明您还活着并且正在等待文件下载。或者你本可以做其他工作......你明白了。