C#异步函数过早返回

Cri*_*on7 0 c# async-await

我正在尝试等待异步函数完成,以便我可以在我的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"));
    }
Run Code Online (Sandbox Code Playgroud)

FormList()中的TitleList = null,因为尚未完成Repopulate().因此我使用了Wait().但是,等待在函数完成之前返回.

我在这做错了什么?

Dou*_*las 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"));
}
Run Code Online (Sandbox Code Playgroud)

更新:为了未来读者的利益,我正在将我的评论重新改写为答案:

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

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

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

通过改变的签名Repopulateasync Task,你现在得到的是代表它的异步执行完成,另一个任务包括query.FindAsync()异步调用,并且成功它的处理.当作为参数Task.Run传递异步操作(Func<Task>)时,其返回的任务将等待(解包)内部任务.这就是为什么Task.Run应该用来代替Task.StartTask.Factory.StartNew.