Почему мое приложение WPF, загружающее анимацию, зависает?

Я разрабатываю настольное приложение для управления каталогом леса. При запуске приложения должны отображаться определенные данные из базы данных MySql, что замедляет запуск приложения, поэтому я хочу показать диалог, который показывает анимированный gif, пока данные не будут загружены. Проблема в том, что приложение зависает и диалоговое окно не отображается до завершения загрузки данных. Я искал другие сообщения, но не могу найти решение. Я ценю любую помощь, спасибо.

Это мой XAML MainWindow:

<mui:ModernWindow x:Class="ModernUINavigationApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mui="http://firstfloorsoftware.com/ModernUI"
Title="Media Copy Manager" IsTitleVisible="True"
WindowStartupLocation="CenterScreen"


ContentSource="/gui/Pages/PHome.xaml" WindowState="Maximized">

<mui:ModernWindow.MenuLinkGroups>
    ( ...)
</mui:ModernWindow.MenuLinkGroups>

Here is my PHome.xaml page:

<UserControl x:Class="MCP.gui.Pages.PHome"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mui="http://firstfloorsoftware.com/ModernUI"
         mc:Ignorable="d">

<Grid Style="{StaticResource ContentRoot}" Margin="0 0 0 0" Name="_contentRoot">
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
     </Grid.ColumnDefinitions>

    <TabControl Grid.Row="0" Grid.Column="0" Name="_TAB" TabStripPlacement="Left"/>
</Grid>

This is my Home.cs code :

{
    public PHome()
    {
        InitializeComponent();
        this.Loaded += ContentLoaded;
    }
    private void ContentLoaded(object sender, RoutedEventArgs e)
    {
         Thread t = new Thread(() =>
         {
             Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
               (Action)(() =>
               {
                   new LoadingDialog().Show();
               }));
         });
         t.SetApartmentState(ApartmentState.STA);
         t.IsBackground = true;
         t.Start();
        Task.Run(() =>
        {
            _TAB.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                (Action)(() =>
                {
                    Populate_Tab(_TAB);
                }));
        });
    }
    private async void Populate_Tab(TabControl tabControl)
    {
        tabControl.Items.Clear();
        tabControl.ClipToBounds = true;

        List<categoria> ListaCategorias = await DBManager.CategoriasRepo.ListAsync;
        foreach (categoria categ in ListaCategorias)
        {
            TabItem tabitem = new TabItem();
            tabitem.Header = categ.categoria1;
            Thread.Sleep(1000);   //Do some long execution process
            tabControl.Items.Add(tabitem);
        }
    }
}

Вот мой LoadingDialog XAML:

                  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                  xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                  xmlns:mui="http://firstfloorsoftware.com/ModernUI"
                  mc:Ignorable="d" d:DesignWidth="300"
                  Title="ModernDialog" Background="{x:Null}" Height="197.368">
    <StackPanel>
        <TextBlock>LOADING</TextBlock>
        <mui:ModernProgressRing IsActive="True" Name="_LoaderGif" Width="100" Height="100" Style="{StaticResource ThreeBounceProgressRingStyle}" Margin="47,43,68,65" />
    </StackPanel>
</mui:ModernDialog>


person Raisel Castellanos Santiago    schedule 07.06.2020    source источник


Ответы (1)


Вам нужно лучше понять методы, которые вы используете, особенно те, которые вы используете, чтобы попытаться перенести вещи в другие потоки. Прежде чем я объясню, имейте в виду следующее:

  • Основной поток по умолчанию в приложении WPF можно назвать потоком пользовательского интерфейса. Весь код для рендеринга и макета приложения обрабатывается в этом потоке. Весь код, который вы пишете, также обрабатывается в этом потоке, если вы не укажете иное.
  • Поток запускает по одному методу за раз, поэтому любой длительный метод в потоке пользовательского интерфейса не позволит ему запустить методы, необходимые для компоновки и рендеринга приложения (т. е. он «зависнет»).
  • Изменения в элементах пользовательского интерфейса — визуальных аспектах приложения — должны выполняться из потока пользовательского интерфейса.

Кажется, вы не понимаете Dispatcher.Invoke.

Точно так же, как Task.Run переносит выполнение кода в новый поток, Dispatcher.Invoke переносит выполнение кода обратно в пользовательский интерфейс.

Возьмем ваш метод ContentLoaded. В своем коде вы пытаетесь использовать классы Thread и Task, чтобы переместить часть вашего кода в отдельный поток. Но все, что вы делаете из этих новых потоков, — это немедленный вызов Dispatcher.Invoke, который выполняет код обратно в потоке пользовательского интерфейса. Теоретически это на самом деле делает ваше приложение медленнее, поскольку оно тратит время на создание новых потоков и беспричинное перескакивание между ними.

Что вам нужно сделать, так это взять Populate_Tab и разделить его на два (или более) разных метода: один, который влияет на пользовательский интерфейс, и другой, который не влияет.

Такие вызовы, как tabControl.Items.Clear и tabitem.Header = categ.categoria1, должны выполняться в потоке пользовательского интерфейса, поскольку они напрямую ссылаются на элементы пользовательского интерфейса, но ваши длительные операции, такие как доступ к вашей базе данных, могут и должны быть перемещены в другой поток.

Последовательность должна быть:

  • Показать значок загрузки.
  • Начать длительную операцию в другом потоке. Я бы рекомендовал использовать Task с async и await, так как это наилучшая практика.
  • Получите все необходимые данные и выполните любые расчеты в этом другом потоке.
  • Верните необходимые данные обратно в поток пользовательского интерфейса (используя await) и обновите пользовательский интерфейс по мере необходимости.
  • Уберите значок загрузки.
person Keith Stein    schedule 07.06.2020
comment
Здравствуйте, спасибо за быстрый ответ. Я нашел решение благодаря вашему объяснению, однако стоит уточнить, что после получения данных при вставке их в визуальный компонент мне приходилось делать это в потоке. Спасибо!!! - person Raisel Castellanos Santiago; 09.06.2020
comment
@RaiselCastellanosSantiago Хорошо. Что ж, если вы считаете, что мой ответ решил вашу проблему, примите мой ответ. - person Keith Stein; 09.06.2020