Task.Run阻塞主线程(导致界面冻结)

5
我正在尝试异步调用以加载数据到我的网格中。问题是,该操作阻塞了UI(在主线程中运行),我不知道原因。我正在尝试在后台获取数据...以下是代码:
将ViewModel类绑定到主窗口的DataContext:
<Window.DataContext>
    <vm:MainWindowViewModel WindowTitle="MVVM" BtnLoadText="LOAD DATA"/>
</Window.DataContext>

在ViewModel类中,将DataGrid的列绑定到集合属性(PeopleList):

    <DataGrid AutoGenerateColumns="False" IsReadOnly="True" ItemsSource="{Binding Path=PeopleList, Mode=TwoWay}" Margin="5">
        <DataGrid.Columns>
            <DataGridTextColumn Header="First name" Binding="{Binding Path=FirstName}"/>
            <DataGridTextColumn Header="Last name" Binding="{Binding Path=LastName}"/>
            <DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"/>
        </DataGrid.Columns>
    </DataGrid>
    <Button x:Name="btn_LoadData" Margin="5" Grid.Row="1" Content="{Binding Path=BtnLoadText}" Click="btn_LoadData_Click"/>

MainWindow 的 Code-Behind - 运行异步按钮点击事件:

public partial class MainWindow : Window
{
    private MainWindowViewModel mainWindowViewModel;
    public MainWindow()
    {
        InitializeComponent();
        mainWindowViewModel = (MainWindowViewModel)DataContext;
    }

    private async void btn_LoadData_Click(object sender, RoutedEventArgs e)
    {
        await mainWindowViewModel.LoadData();
    }
}

ViewModel类负责MainWindow:

class MainWindowViewModel
{
    public string WindowTitle { get; set; }
    public string BtnLoadText { get; set; }

    public ObservableCollection<Person> PeopleList { get; set; }

    private Database database = new Database();

    public MainWindowViewModel()
    {
        PeopleList = new ObservableCollection<Person>();
    }

    public async Task LoadData()
    {
        PeopleList.Clear();

        var result = await database.GetPeopleListLongOperationAsync();

        PeopleList.Add(result.First());
    }
}

正如你所看到的,我正在使用LoadData方法进行异步调用,该方法从数据库获取数据并将其添加到ObservableCollection中,这将更新DataGrid(从DataGrid绑定)。
“模拟”数据获取的数据库类:
public class Database
{
    public IEnumerable<Person> GetPeopleListLongOperation()
    {
        // forcing "long" data load
        Thread.Sleep(5000);
        yield return new Person() { FirstName = "Name", LastName = "LastName", Age = new Random().Next(18, 40) };
    }

    public Task<IEnumerable<Person>> GetPeopleListLongOperationAsync()
    {
        return Task.Run<IEnumerable<Person>>(() =>
        {
            return GetPeopleListLongOperation();
        });
    }
}

我正在使用Task.Run在后台线程中获取数据。问题是-它在主线程中运行,并且阻塞了UI。
有什么建议吗?我不确定自己是否正确理解异步操作...
编辑
将任务结果类型从IEnumerable更改为List使其正常工作。有人能解释一下吗?
    public Task<List<Person>> GetPeopleListLongOperationAsync()
    {
        return Task.Run<List<Person>>(() =>
        {
            return GetPeopleListLongOperation().ToList();
        });
    }

我不确定IEnumerable和List之间的区别。尽管如此,我认为使用"public async Task<List<Person>> GetPeopleListLongOperationAsync()"而不是"public Task<List<Person>> GetPeopleListLongOperationAsync()"会更有帮助,以及使用"return Task.Run<List<Person>>(async () => { return GetPeopleListLongOperation().ToListAsync(); });"而不是"Task.Run<List<Person>>(() => { return GetPeopleListLongOperation().ToList(); });" - user8128167
1个回答

5

现在暂时不要考虑线程的复杂性,只需要看这段代码:

public IEnumerable<Person> GetPeopleListLongOperation()
{
    // forcing "long" data load
    Thread.Sleep(5000);
    yield return new Person();
}

当您调用GetPeopleListLongOperation()时,它将立即返回,不会等待5秒钟。这是因为像这样的迭代器块是惰性计算的; 只有当您枚举序列时才会调用Thread.Sleep(5000)
现在您已经理解了这一点,您应该知道您的“修复”起作用是因为ToList将枚举序列完成,同时仍在线程池线程上返回缓存结果。如果没有这个,您将在调用result.First()时在UI线程上枚举序列并阻塞它。

谢谢。 我知道IEnumerable集合的延迟加载,但我不知道它是这样工作的。 - asiesom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接