C#异步函数过早返回

时间:2016-03-24 22:36:03

标签: c# async-await

我正在尝试等待异步函数完成,以便我可以在我的UI线程中填充ListView。

这是代码

-location
     - userID
          - g
          - l
            - 0:lat
            - 1:long

- user
    -userID
       - ...
由于Repopulate()尚未完成,因此Form1()中的TitleList = null。因此我使用了Wait()。但是,等待在函数完成之前返回。

我在这里做错了什么?

1 个答案:

答案 0 :(得分:7)

您需要更改Repopulate方法的返回类型,以返回表示异步操作的任务。

此外,您不应该从表单构造函数执行异步操作,因为调用Task.Wait会导致UI线程被阻塞(并且您的应用程序看起来没有响应)。相反,订阅其Form.Load方法,并在那里执行异步操作,使用await关键字保持事件处理程序异步。如果您不希望用户与表单交互,直到异步操作完成,则在构造函数中禁用表单并在Load处理程序的末尾重新启用它。

private async void Form1_Load(object sender, EventArgs e)
{
    Task t = Repopulate();
    // If you want to run its synchronous part on the thread pool:
    // Task t = Task.Run(() => Repopulate());

    // some other work here
    await t;
}

async Task Repopulate()
{           
    var query = ParseObject.GetQuery("Offer");
    IEnumerable<ParseObject> results = await query.FindAsync();

    if (TitleList == null)
        TitleList = new List<string>();
    foreach (ParseObject po in results)
        TitleList.Add(po.Get<string>("title"));
}

更新:为了未来读者的利益,我将评论重新编写到答案中:

Task.Wait导致调用线程阻塞,直到任务完成。表单构造函数和事件处理程序,就其性质而言,在UI线程上运行,因此在其中调用Wait将导致UI线程被阻塞。另一方面,await关键字将导致当前方法将控制权交还给调用者 - 在事件处理程序的情况下,这将允许UI线程继续处理事件。然后,等待方法(事件处理程序)将在任务完成后在UI线程上恢复执行。

Task.Wait将始终阻止调用线程,无论是从构造函数还是事件处理程序调用,因此应该避免使用它,尤其是在UI线程上运行时。 C#5为此引入了asyncawait个关键字;但是,他们只支持方法,而不是构造函数。此限制是您需要将初始化代码从表单构造函数移动到异步事件处理程序的主要原因。

至于Task.Wait()过早返回的原因:在原始代码中,任务t表示您在表单构造函数中实例化的Task的执行。此任务运行Repopulate;但是,所述方法一旦遇到第一个await语句就会返回,并以一种即发即忘的方式执行其余的逻辑。这是使用async void的危险 - 您不知道异步方法何时完成执行。 (因此,async void只应用于事件处理程序。)换句话说,t.Wait()会在Repopulate点击其第一个await后立即返回。

通过将Repopulate的签名更改为async Task,您现在将获得另一个表示其异步执行完成的任务,包括 query.FindAsync()异步调用和成功的处理。当Task.Run作为参数传递异步操作(Func<Task>)时,其返回的任务将等待(解包)内部任务。这就是为什么应该使用Task.Run而不是Task.StartTask.Factory.StartNew