Task.Run阻塞主线程(冻结的UI)

时间:2022-08-27 20:57:18

I'm trying to make an asynchronous call to load data to my grid. Problem is, operation is blocking the UI (running in the main thread) - and i don't know why. I'm trying to make it to fetch data in the background ... Here's the code:

我正在尝试进行异步调用以将数据加载到网格中。问题是,操作阻止了UI(在主线程中运行) - 我不知道为什么。我正试图让它在后台获取数据......这是代码:

Binding ViewModel class to DataContext of the main window:

将ViewModel类绑定到主窗口的DataContext:

<Window.DataContext>
    <vm:MainWindowViewModel WindowTitle="MVVM" BtnLoadText="LOAD DATA"/>
</Window.DataContext>

DataGrid with column binding to the collection property (PeopleList) in ViewModel class:

DataGrid与列绑定到ViewModel类中的集合属性(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"/>

Code-Behind of the MainWindow - running asynchronous button click event:

MainWindow的代码隐藏 - 运行异步按钮单击事件:

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 class responsible for MainWindow:

负责MainWindow的ViewModel类:

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());
    }
}

As you see, i'm making an asynchronous call with LoadData method, which gets data from the database and adding it to ObservableCollection, which updates DataGrid (Binding from the DataGrid)

如你所见,我正在使用LoadData方法进行异步调用,该方法从数据库获取数据并将其添加到ObservableCollection,后者更新DataGrid(从DataGrid绑定)

Database class, which "emulates" data fetching:

数据库类,“模拟”数据获取:

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();
        });
    }
}

I'm using Task.Run to fetch data in background thread. The problem is - it is running in Main Thread and it's blocking the UI.

我正在使用Task.Run来获取后台线程中的数据。问题是 - 它在主线程中运行,它阻止了UI。

Any suggestions? I'm not sure anymore, if i understand async operations correctly ...

有什么建议?如果我正确理解异步操作,我不再确定了...

EDIT

Changing Task result type from IEnumerable to List made it work. Can someone explain me why?

将任务结果类型从IEnumerable更改为List使其工作。有人能解释我为什么吗?

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

1 个解决方案

#1


5  

Forget about the threading complication for now, and just take this code:

暂时忘掉线程并发症,只需要这段代码:

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

When you call GetPeopleListLongOperation() it will return immediately, it won't wait 5 seconds. This is because iterator blocks like this are lazily evaluated; Thread.Sleep(5000) will only be called when you enumerate through the sequence.

当你调用GetPeopleListLongOperation()它会立即返回,它不会等待5秒。这是因为像这样的迭代器块被懒惰地评估了;只有在枚举序列时才会调用Thread.Sleep(5000)。

Now you understand that, you should see that you 'fix' works because ToList will enumerate the sequence to completion while still on the thread pool thread and return the cached results. Without this you were enumerating the sequence on the UI thread on the call to result.First() and blocking it.

现在你明白了,你应该看到你'修复'有效,因为ToList会在线程池线程上枚举完成序列并返回缓存结果。如果没有这个,你就会在调用result.First()并阻塞它时枚举UI线程上的序列。

#1


5  

Forget about the threading complication for now, and just take this code:

暂时忘掉线程并发症,只需要这段代码:

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

When you call GetPeopleListLongOperation() it will return immediately, it won't wait 5 seconds. This is because iterator blocks like this are lazily evaluated; Thread.Sleep(5000) will only be called when you enumerate through the sequence.

当你调用GetPeopleListLongOperation()它会立即返回,它不会等待5秒。这是因为像这样的迭代器块被懒惰地评估了;只有在枚举序列时才会调用Thread.Sleep(5000)。

Now you understand that, you should see that you 'fix' works because ToList will enumerate the sequence to completion while still on the thread pool thread and return the cached results. Without this you were enumerating the sequence on the UI thread on the call to result.First() and blocking it.

现在你明白了,你应该看到你'修复'有效,因为ToList会在线程池线程上枚举完成序列并返回缓存结果。如果没有这个,你就会在调用result.First()并阻塞它时枚举UI线程上的序列。