异步任务- CPU上实际发生的事情



在问了这个问题之后,我一直在阅读Task s,发现我完全误解了这个概念。诸如这里和这里的顶级答案解释了的想法,但我仍然不明白。所以我提出了一个非常具体的问题:当Task被执行时,CPU到底发生了什么?

这是我读了一些后所理解的:一个任务将与调用者共享CPU时间(让我们假设调用者是"UI"),所以如果它是CPU密集型的-它将减慢UI。如果Task 不是 cpu密集型任务,它将在"后台"运行。似乎足够清楚......直到测试。下面的代码应该允许用户点击按钮,然后交替显示"已显示"one_answers"按钮"。但在现实中:表单是完全忙碌的(没有用户输入的可能),直到"显示"的所有显示。

public Form1()
{
    InitializeComponent();
    Shown += Form1_Shown;
}
private async void Form1_Shown(object sender, EventArgs e)
{
    await Doit("Shown");
}
private async Task Doit(string s)
{
    WebClient client = new WebClient();
    for (int i = 0; i < 10; i++)
    {
        client.DownloadData(uri);//This is here in order to delay the Text writing without much CPU use.
        textBox1.Text += s + "rn";
        this.Update();//textBox1.
    }
}
private async void button1_Click(object sender, EventArgs e)
{
    await Doit("Button");
}

有人能告诉我什么是实际发生在CPU上的任务执行时(例如:"当CPU不被UI使用时,任务使用它,除了当…等")?

理解这一点的关键是有两种任务 -一种执行代码(我称之为委托任务),另一种代表未来事件(我称之为承诺任务)。这两个任务是完全不同的,即使它们都由。net中的Task实例表示。我的博客上有一些漂亮的图片,可以帮助你理解这些类型的任务是如何不同的。

委托任务是由Task.Run和朋友创建的任务。它们在线程池上执行代码(如果使用TaskFactory,也可能是另一个TaskScheduler)。大多数"任务并行库"文档处理委托任务。它们用于在多个cpu上分散cpu绑定的算法,或者将cpu绑定的工作从UI线程中推送出去。

承诺任务是由TaskCompletionSource<T>和好友(包括async)创建的任务。它们是用于异步编程的,并且非常适合I/o绑定代码。

注意,您的示例代码将导致编译器警告,大意是您的"异步"方法Doit实际上不是异步的,而是同步的。所以现在,它将同步地调用DownloadData,阻塞UI线程,直到下载完成,然后它将更新文本框,最后返回一个已经完成的任务。

要使其异步,必须使用await:

private async Task Doit(string s)
{
  WebClient client = new WebClient();
  for (int i = 0; i < 10; i++)
  {
    await client.DownloadDataTaskAsync(uri);
    textBox1.Text += s + "rn";
    this.Update();//textBox1.
  }
}

现在它返回一个未完成的任务,当它到达await,这允许UI线程返回到它的消息处理循环。当下载完成时,该方法的其余部分将作为消息排在UI线程的队列中,当它到达时,它将继续执行该方法。当Doit方法完成时,它先前返回的任务也将完成。

因此,async方法返回的任务在逻辑上表示该方法。任务本身是Promise task,而不是Delegate task,并且实际上并不"执行"。该方法被分成多个部分(在每个await点)并在块中执行,但任务本身不会在任何地方执行。

为了进一步阅读,我有一篇关于asyncawait如何实际工作(以及它们如何调度方法的块)的博客文章,以及另一篇关于为什么异步I/O任务不需要阻塞线程的博客文章。

根据你的链接答案,任务和线程是完全不同的概念,你也会混淆async/await

Task只是一些要完成的工作的表示。

Thread表示一些正在CPU上运行的工作,但它与其他线程共享CPU时间,而它对这些线程一无所知。

可以使用Task.Run()Thread上运行Task。您的Task将异步运行,并独立于任何其他代码,提供线程池线程可用。

你也可以使用async/await在SAME线程上异步运行Task。每当线程遇到await时,它都可以保存当前堆栈状态,然后返回堆栈并继续执行其他工作,直到等待的任务完成。您的Doit()代码从不等待任何东西,因此将在GUI线程上同步运行,直到完成。

任务使用ThreadPool,您可以在这里广泛了解它是什么以及它是如何工作的

但简而言之,当一个任务被执行时,task Scheduler查看ThreadPool,看看是否有一个线程可用来运行任务的动作。如果没有,它将被排队,直到有一个可用。

ThreadPool只是一个已经实例化的线程的集合,使得多线程代码可以安全地使用并发编程,而不会因为上下文切换而使CPU一直超负荷。

现在,您的代码的问题是,即使您返回类型为Task的对象,您也没有并发运行任何东西-没有单独的线程被启动!

为了做到这一点,你有两个选择,要么你启动你的Doit方法作为一个任务,与

Option1

Task.Run(() => DoIt(s));

这将在线程池中的另一个线程上运行整个DoIt方法,但它将导致更多问题,因为在此方法中,您试图访问ui控件。因此,您需要将这些调用封送到UI线程,重新考虑您的代码,以便在异步任务完成后直接在UI线程上完成UI访问。


选项2(如果可以,首选)

你使用的。net api 已经是异步的,比如client.DownloadDataTaskAsync();而不是client.DownloadData();

现在,在您的例子中,问题是您需要有10个调用,它们将返回10个不同类型的Task<byte[]>对象,并且您希望等待所有调用的完成,而不仅仅是一个。

为了做到这一点,您需要创建一个List<Task<byte[]>> returnedTasks,并将从DownloadDataTaskAsync()返回的所有值添加到它。然后,一旦完成,您可以为您的DoIt方法使用以下返回值:

return Task.WhenAll(returnedTasks);

最新更新