【问题标题】:C# Async Function returning prematurelyC# 异步函数过早返回
【发布时间】:2016-03-24 22:36:03
【问题描述】:

我正在尝试等待异步函数完成,以便在我的 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()。但是,在函数完成之前等待就返回了。

我在这里做错了什么?

【问题讨论】:

    标签: c# .net async-await task-parallel-library


    【解决方案1】:

    您需要更改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"));
    }
    

    更新:为了未来读者的利益,我将我的 cmets 改写为答案:

    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&lt;Task&gt;)作为参数时,其返回的任务将等待(解包)inner 任务。这就是为什么应该使用Task.Run 而不是Task.StartTask.Factory.StartNew

    【讨论】:

    • 这仍在使用阻塞Task.Wait() 调用
    • @kai:对此表示歉意;现已修复。
    • 效果很好,谢谢。你能告诉我为什么 1) Task.Wait() 使 UI 线程无响应吗? (与等待相反) 2)Task.Wait() 过早返回? (原代码)
    • @Nick_McCoy: Task.Wait 导致调用线程阻塞,直到任务完成。表单构造函数和事件处理程序本质上是在 UI 线程上运行的,因此在它们内部调用 Wait 会导致它阻塞。另一方面,await 关键字将放弃控制权,允许 UI 线程继续处理事件;然后在任务完成后恢复执行。
    • @Douglas 我在事件处理程序中使用了 Task.Wait()(而不是 await),但它仍然让我的 UI 无响应。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    • 2021-02-23
    • 1970-01-01
    • 2022-01-14
    • 2019-02-05
    相关资源
    最近更新 更多