C# 异步函数过早返回

C# Async Function returning prematurely

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

这是代码

    public Form1()
    {
        InitializeComponent();

        Task t = new Task(Repopulate);
        t.Start();
        // some other work here
        t.Wait(); //completes prematurely

    }

    async void 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"));
    }

Form1() 中的 TitleList = null 因为 Repopulate() 尚未完成。因此我使用了 Wait()。但是,在功能完成之前等待 returns。

我做错了什么?

您需要将 Repopulate 方法的 return 类型更改为 return 表示异步操作的任务。

此外,您不应从表单构造函数执行异步操作,因为调用 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 将始终阻塞调用线程,无论是从构造函数还是事件处理程序调用,因此应避免使用它,尤其是当 运行ning 在 UI 上时线。为此,C# 5 引入了 asyncawait 关键字;但是,它们仅在方法上受支持,在构造函数上不受支持。此限制是您需要将初始化代码从表单构造函数移至异步事件处理程序的主要原因。

至于Task.Wait() return过早的原因: 在你原来的代码中,task t 代表你在表单中实例化的 Task 的执行构造函数。这个任务运行s Repopulate;但是,一旦遇到第一个 await 语句,上述方法就会 return ,并以即发即弃的方式执行其其余逻辑。这就是使用 async void 的危险——您将不知道异步方法何时完成执行。 (出于这个原因,async void 应该只用于事件处理程序。)换句话说,t.Wait() return 一旦 Repopulate 命中它的第一个 await .

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