如何在后台运行RunAsync()方法而不调用Wait()?

时间:2016-06-16 15:33:40

标签: c# async-await task backgroundworker

尝试从使用BackgroundWorker升级到Task,我对如何在不调用Wait()的情况下保持后台作业运行感到困惑。

这是后台工作(简化):

    private async Task RunAsync()
    {
        HttpResponseMessage response = await TheHttpClient.GetAsync(Path);
        if (response.IsSuccessStatusCode)
        {
            textBox_Response.Text = await response.Content.ReadAsStringAsync();
        }
    }

单击“发送”按钮时,上面的代码应该运行,但由于后台代码有多个await异步调用,我猜需要进行多次Wait()次调用:

    private void button_Send_Click(object sender, EventArgs e)
    {
        Task t = RunAsync();
        while (!t.IsCompleted)
        {
            t.Wait();
        }
    }

但是这会在后台作业的整个处理时间内阻止GUI。

如何启动后台作业并立即从按钮单击处理程序返回,同时允许后台作业运行所有异步调用?

我知道如何使用BackgroundWorker实现这一点,我不应该在这里使用任务吗?

2 个答案:

答案 0 :(得分:6)

事件处理程序是您唯一允许执行<Connector protocol="org.apache.coyote.http11.Http11Protocol" port="8080" redirectPort="8443" <Connector protocol="org.apache.coyote.http11.Http11Protocol" port="8443" SSLEnabled="true" scheme="https" secure="true" sslProtocol="TLS" keystorePass="<pass>" keystoreFile="<path to keystore>" ciphers="TLS_RSA_WITH_AES_256_CBC_SHA, <add more ciphers>"/> 的地方,您所做的是使事件处理程序异步并等待它

async void

答案 1 :(得分:1)

人们常常认为async await是由多个线程执行的异步进程,但实际上它是由一个线程完成的,除非你使用Task.Run或类似函数启动一个新线程。

this interview(中间的某个位置。搜索异步)Eric Lippert比较了餐厅厨师的作品的异步等待。如果他正在烤面包,他可以在煮鸡蛋之前等待面包准备好,或者他可以开始煮鸡蛋然后再回到面包里。看起来厨师当时做了两件事,但实际上他只做了一件事,每当他不得不等待什么时,他就开始环顾四周,看看他是否可以做其他事情。

如果您的唯一线程调用异步函数,而不等待它。你的唯一线程开始执行该函数,直到他看到等待。如果他看到一个,他就不会等到异步功能完成,而是记得他在等待的地方,然后在他的调用堆栈中查看他的调用者是否还有别的事情要做(==不等待)。调用者可以进行下一个语句,直到他遇到等待。在那种情况下,控制被给予调用者回调给调用者,直到他等待等等。如果你的唯一线程正在等待调用栈控制中的所有函数,则返回到第一个await。完成后,你的线程在等待之后开始执行语句,直到他遇到另一个await,或者直到函数完成。

您可以确定每个异步函数都在等待某个地方。如果没有等待,则将其创建为异步函数没有意义。事实上,如果您忘记等待异步函数中的某个地方,您的编译器会发出警告。

在您的示例中,您可能会遇到错误,或者至少是您忘记声明button_send_click异步的警告。如果没有这个,程序就等不及了。

但是在你宣布它是异步的并且按下按钮之后,你的GUI线程将调用RunAsync,它将进入函数TheHttpClient.GetAsync。

在此函数内部等待ReadAsStringAsync,并且因为ReadAsStringAsync被声明为异步,我们可以确定该函数有一个等待。只要满足此等待时间,就会将控制权返回给您的TheHttpClient.GetAsync。如果没有等待,该函数可以自由执行下一个语句。一个例子是这样的:

private async Task RunAsync()
{
    var responseTask = TheHttpClient.GetAsync(Path);
    // because no await, your thread will do the following as soon as GetAsync encounters await:
    DoSomethingUseFul();

    // once your procedure has nothing useful to do anymore
    // or it needs the result of the responseTask it can await:
    var response = await responseTask;
    if (response.IsSuccessStatusCode)
    ...

await在DoSomethingUseful()之后。因此,该功能无法继续。在responseTask完成之前,不会执行任何操作,而是将控制权返回给调用者:button_send_click。如果该功能没有等待它可以自由地做其他事情。但是,因为等待控制的button_send_click被返回给调用者:你的UI可以自由地做其他事情。

但请记住:永远是独一无二的厨师做早餐你的线程一次只做一件事

优势在于您不会遇到多线程遇到的困难。缺点是,只要你的线程没有满足等待,它就太忙了,无法做其他事情。

如果您不想在忙着进行计算时需要进行一些冗长的计算,可以启动一个单独的线程来进行此计算。这使您的线程有时间做其他事情,比如保持UI响应,直到它需要计算结果:

private async void Button1_Clicked(object sender, ...)
{
    var taskLengthyCalculation = Task.Run( () => DoLengthyCalculation(...));
    // while a different thread is doing calculations
    // I am free to do other things:
    var taskRunAsync = RunAsync();
    // still no await, when my thread has to await in RunAsync,
    // the next statement:
    DoSomethingUseful();
    var result = await taskRunAsync();
    var resultLengthyCalculation = await taskLengthyCalculation;