【问题标题】:Task.Run then Invoke on main thread alternative using async await ContinueWith?Task.Run 然后使用异步等待 ContinueWith 在主线程替代上调用?
【发布时间】:2018-09-14 11:11:44
【问题描述】:

以下代码完美运行。它在 UI 上显示微调器,使用线程池中的线程启动任务并运行繁重的操作,一旦完成,隐藏微调器的逻辑将按预期在主线程上执行。

    public void LoadCustomers()
    {
        // Update UI to show spinner
        this.LoadingCustomers = true;

        Task.Run(async () =>            
        {
            var customers = await this.custService.GetCustomers();
            // code truncated for clarity

            Device.BeginInvokeOnMainThread(() => 
            {
                // Update UI to hide spinner
                this.LoadingCustomers = false;
            });
        });
    }

我的问题;有没有更好的方法来使用 ContinueWith/ConfigureAwait 选项编写此逻辑?使用这些选项似乎会阻塞 UI 线程。在下面的示例中,UI 线程是否应该继续运行 UI 逻辑(动画微调器/用户输入)然后回来完成 ​​ContinueWith 内部的逻辑?

    public void LoadCustomers()
    {
        // Update UI to show spinner
        this.LoadingCustomers = true;

        this.custService.GetCustomers().ContinueWith((t) =>
        {
            var customers = t.Result;
            // code truncated for clarity

            // Update UI to hide spinner
            this.LoadingCustomers = false;
        });
    }

根据 cmets 的要求,这里是 GetCustomers 的代码。 dbContext 是 EntityFrameworkCore。

    public async Task<List<CustomerModel>> GetCustomers()
    {
        return await this.dbContext.Customers.ToListAsync();
    }

更新

但是,FCin 的答案是正确的;原因似乎是 EFCore 和 ToListAsync,它没有异步运行。

【问题讨论】:

  • GetCustomers 是如何实现的?它可能在返回 Task 之前被阻塞。您还可以将LoadCustomers 设为async Task,从而避免必须显式使用ContinueWith(或async void,如果您需要它作为特定类型的处理程序)
  • 什么框架? ASP.NET? wpf? winform?
  • 我已经用 GetCustomers 方法更新了问题,它是一个 Xamarin Forms 项目
  • 如果GetCustomers 已经是支持Taskasync,为什么还要使用Task.Run,而不是直接等待它呢?编译器会自动将您的 async 方法转换为幕后的一系列延续。这正是 async / await 的设计目的。
  • [...]除非// code truncated for clarity 中有需要不同线程的内容。如果只是“普通”代码,我强烈建议直接等待;您根本不必担心跨线程编组。

标签: c# multithreading asynchronous xamarin.forms


【解决方案1】:

编写这种方法的正确方法是从头到尾使用async/await。现在,如果Task.Run 内部有异常,您将永远不会知道它的含义。您应该从事件处理程序开始。这可以是任何东西,鼠标点击、页面加载等等。

private async void MouseEvent_Click(object sender, EventArgs args)
{
    await LoadCustomers();
}

public async Task LoadCustomers()
{
    // Update UI to show spinner
    this.LoadingCustomers = true;

    // We don't need Device.BeginInvokeOnMainThread, because await automatically 
    // goes back to calling thread when it is finished
    var customers = await this.custService.GetCustomers();

    this.LoadingCustomers = false;
}

有一种简单的方法可以记住何时使用Task.Run在您执行 CPU 受限的操作时使用Task.Run,例如计算 PI 的位数。

【讨论】:

  • 您需要将异步添加到事件处理程序签名。
【解决方案2】:

编辑:@bradley-uffner 建议只写以下内容:

public async Task LoadCustomers()
{
    // Update UI to show spinner
    this.LoadingCustomers = true;

    var customers = await this.custService.GetCustomers();
    // code truncated for clarity

    // you are still on UI thread here
    this.LoadingCustomers = false;
}

这个怎么样:

public async Task LoadCustomers()
{
    // Update UI to show spinner
    this.LoadingCustomers = true;

    await Task.Run(async () =>            
    {
        var customers = await this.custService.GetCustomers();
        // code truncated for clarity
    });

    this.LoadingCustomers = false;
}

await 之后的代码在当前线程上执行,因此它应该可以立即使用。

【讨论】:

  • 如果this.custService.GetCustomers() 已经是支持Taskasync,为什么还要使用Task.Run,而不是直接等待呢?
  • 实际上我什至没有检查代码的内容 :D 让我来解决这个问题。
猜你喜欢
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 2016-03-14
  • 1970-01-01
  • 2012-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多