Отображение диалогового окна ожидания с анимацией при загрузке данных из SQL

Я сделал приложение для просмотра, редактирования и создания данных, хранящихся на SQL-сервере. Моя проблема в том, что взаимодействие с SQL Server может занять некоторое время, тем более что программу используют и мои коллеги в Китае, а мой сервер находится в Германии. Итак, я хочу показать диалог с анимацией ожидания во время загрузки данных. Нет необходимости продолжать работать с моим графическим интерфейсом во время загрузки. Моим первым решением было сделать этот класс:

public class LongTimeAction
{
    public void Run(Action MyMethod)
    {
        try
        {
            var thread = new Thread(() =>
            {
                Dispatcher.CurrentDispatcher.BeginInvoke((Action)(() => new MyWindows.CMN_Splash().Show()));
                Dispatcher.Run();
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
            MyMethod();
            thread.Abort();
        }
        catch (Exception exp)
        {
            new Errorlogger().write_Error(this.GetType().Name, System.Reflection.MethodBase.GetCurrentMethod().Name, exp.Message);
        }
    }
}

Окно-заставка имеет анимацию в стиле «Рыцарь дорог», созданную на XAML, которая запускается автоматически при загрузке окна:

    <Window.Triggers>
    <EventTrigger RoutedEvent="Window.Loaded">
        <BeginStoryboard>
            <Storyboard RepeatBehavior="Forever" Duration="0:0:1.4">
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar1" Storyboard.TargetProperty="Opacity">
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.5" />
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar2" Storyboard.TargetProperty="Opacity">
                    <DiscreteDoubleKeyFrame Value="0.8" KeyTime="0:0:0" />
                    <LinearDoubleKeyFrame Value="0.6" KeyTime="0:0:0.1" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.1" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.6" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:1.3" />
                    <LinearDoubleKeyFrame Value="0.8" KeyTime="0:0:1.4" />
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar3" Storyboard.TargetProperty="Opacity">
                    <DiscreteDoubleKeyFrame Value="0.6" KeyTime="0:0:0" />
                    <LinearDoubleKeyFrame Value="0.2" KeyTime="0:0:0.2" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.2" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.7" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:1.2" />
                    <LinearDoubleKeyFrame Value="0.6" KeyTime="0:0:1.4" />
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar4" Storyboard.TargetProperty="Opacity">
                    <DiscreteDoubleKeyFrame Value="0.4" KeyTime="0:0:0" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.3" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.3" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.8" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:1.1" />
                    <LinearDoubleKeyFrame Value="0.4" KeyTime="0:0:1.4" />
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar5" Storyboard.TargetProperty="Opacity">
                    <DiscreteDoubleKeyFrame Value="0.2" KeyTime="0:0:0" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.1" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.4" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.9" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:1" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:1.4" />
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar6" Storyboard.TargetProperty="Opacity">
                    <DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.5" />
                    <LinearDoubleKeyFrame Value="0.2" KeyTime="0:0:0.9" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.9" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:1.3" />
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar7" Storyboard.TargetProperty="Opacity">
                    <DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.6" />
                    <LinearDoubleKeyFrame Value="0.6" KeyTime="0:0:0.8" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.8" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:1.2" />
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar8" Storyboard.TargetProperty="Opacity">
                    <DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
                    <DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.7" />
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:1.1" />
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger> 
</Window.Triggers>
<Canvas Grid.Row="1" HorizontalAlignment="Stretch" Height="100" Width="275">            
    <TextBlock Canvas.Top="12" Canvas.Left="39" FontSize="32" FontWeight="Bold" Foreground="Gray" Text="Loading Data" />
    <TextBlock Canvas.Top="10" Canvas.Left="37" FontSize="32" FontWeight="Bold" Foreground="White" Text="Loading Data" />
    <Rectangle Canvas.Top="67" Canvas.Left="15" Name="Rect"  Width="245" Height="20" Stroke="White" RadiusY="2" RadiusX="2" StrokeThickness="2" Fill="Black" />
    <Rectangle Canvas.Top="70" Canvas.Left="18" Name="Bar1" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29"  />           
    <Rectangle Canvas.Top="70" Canvas.Left="48" Name="Bar2"  Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
    <Rectangle Canvas.Top="70" Canvas.Left="78" Name="Bar3"  Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
    <Rectangle Canvas.Top="70" Canvas.Left="108" Name="Bar4"  Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
    <Rectangle Canvas.Top="70" Canvas.Left="138" Name="Bar5"  Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
    <Rectangle Canvas.Top="70" Canvas.Left="168" Name="Bar6"  Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
    <Rectangle Canvas.Top="70" Canvas.Left="198" Name="Bar7"  Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
    <Rectangle Canvas.Top="70" Canvas.Left="228" Name="Bar8"  Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
</Canvas>

Это работало хорошо, но не идеально. Иногда моя программа полностью вылетает («Приложение не отвечает»), поэтому я искал лучшее решение. Поскольку нет необходимости взаимодействовать с моим графическим интерфейсом во время загрузки, я изменил свою программу на асинхронное ожидание. Поэтому я изменил все свои функции, которые взаимодействуют с SQL, на асинхронные. После этого я изменил все функции, вызывающие эти функции, на асинхронные и так далее. Теперь по крайней мере половина моей программы асинхронна :-) Я также изменил свой класс LongTimeAction следующим образом:

public static class LongTimeAction
{
    public static async Task Run(Func<Task> MyMethod)
    {
        MyWindows.CMN_Splash MySplashWindow = new MyWindows.CMN_Splash();
        MySplashWindow.Show();

        await MyMethod();

        MySplashWindow.Close();
    }
}

Это называется так:

await LongTimeAction.Run(VT_ReloadAllAction);

В принципе работает, но анимация в заставке не плавная. Также иногда программа полностью зависает.

Не могли бы вы помочь мне выяснить, в чем моя проблема?

ИЗМЕНИТЬ:

Теперь я загрузил расширенный набор инструментов WPF и добавил его в свое главное окно:

<wpfx:BusyIndicator Name="BusyBar" BusyContent="Loading Data..." />

В моем коде я добавил эту функцию

public async Task StartLongRunningTask(Func<Task> MyMethod)
{
    BusyBar.IsBusy = true;
    await MyMethod();
    BusyBar.IsBusy = false;
}

который я называю так:

await StartLongRunningTask(TT_ReloadAllAction);

Мое приложение работает, но BusyIndicator не отображается. Что мне не хватает?

ИЗМЕНИТЬ2 + 4

У меня все еще есть проблема, что моя программа зависает, если я запускаю ее, а затем использую другие окна. Когда я возвращаюсь к своему окну, оно полностью зависает, и я даже не могу его закрыть. Решено. У меня была проблема в моей программе опроса, которая проверяет базу данных каждые минуты просмотра на наличие специального флага. Я сделал это с помощью DispatcherTimer, и это в сочетании с асинхронностью кажется немного сложным. поэтому я изменил это обратно на синхронизацию. Теперь он работает нормально.

ИЗМЕНИТЬ3

Вот мой окружающий код:

public ICommand Cmd_OnlyMyItems { get; set; }
Cmd_OnlyMyItems = new CMN_RelayCommand(Execute => CMN_OnlyMyItems(), CanExecute => true);

private async void CMN_OnlyMyItems()
{
    //some Code here
    await StartLongRunningTask(VT_ApplyFilterAction);
    //some Code here
}

private async Task VT_ApplyFilterAction()
{
    await DBConnect.VT_Get_FilteredData_V2();
    VT_RefreshGrid();
}

public static class DBConnect
{
    public async static Task VT_Get_FilteredData_V2()
    {
        //some Code here
        SqlDataReader reader = command.ExecuteReader()
        while (await reader.ReadAsync())
        {
            //some Code here
        }
    }
}

Теперь я рассмотрю примеры, представленные в разделе комментариев.


person Suiram83    schedule 26.09.2018    source источник
comment
Просто примечание: Thread.Abort — чистое зло. Никогда больше не используйте его в своем коде. См., например. здесь для получения дополнительной информации.   -  person dymanoid    schedule 26.09.2018
comment
Вы не можете использовать BusyIndicator? Это очень просто и аккуратно реализовать, если вам просто нужно предотвратить зависание экрана.   -  person Sham    schedule 26.09.2018
comment
Код с Thread.Abort писал не я. Взяла откуда-то из интернета. Возможно, это и есть причина сбоев.   -  person Suiram83    schedule 26.09.2018
comment
Есть ли другая возможность предотвратить Thread.Abort в моем первом решении?   -  person Suiram83    schedule 26.09.2018
comment
Как определяется MyMethod?   -  person mm8    schedule 26.09.2018
comment
Я предполагаю, что вы не использовали ConfigureAwait(false) для ожидания внутри MyMethod. Это всегда будет синхронизироваться с потоком пользовательского интерфейса. Эта синхронизация необходима только на самом уровне пользовательского интерфейса, чтобы показать/скрыть индикатор занятости. Прочитайте Не блокировать асинхронный код   -  person Sir Rufo    schedule 27.09.2018
comment
@ Suiram83 Посмотрите этот пример проекта github.com/SirRufo/so/tree/master. /52514615/Индикатор занятости   -  person Sir Rufo    schedule 27.09.2018
comment
Я нашел решение для своей первой версии без Thread.Abort здесь< /а>   -  person Suiram83    schedule 28.09.2018