Click here to Skip to main content
15,867,453 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
Hi,
I used the Dispatcher property to modify the UI, and I added System.Threading.Thread.Sleep(3000) in Dispatcher.Invoke block to ensure that it would not freeze, but the UI stays frozen for 3 seconds.
Let's say the Dispatcher.Invoke block takes 2 seconds for some reason, and we don't want the UI to freeze.
The 50ms Rule: Operations should be made asynchronous if they “could take longer than 50 milliseconds to execute”.
According to the above, what is the best way to modify WPF's user interface without freezing it?

These are my C# codes:
C#
private async void Add_Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    await Task.Run(() =>
    {
        OleDbCommand OleDbCommandInsert = null;
        MessageWindow MW = null;
        Dispatcher.Invoke(() =>
        {
            OleDbParameter Parameter = new OleDbParameter();
            Parameter.OleDbType = OleDbType.Binary;
            Parameter.ParameterName = "BookImage";
            Parameter.Value = ImageToBytes((BitmapImage)BookImage.Source);
            OleDbCommandInsert = new OleDbCommand("Insert Into BookInfo(BookName,Authors,Publisher,[ISBN-13],[ISBN-10],Category,PublicationDate,BookLanguage,Length,[Type],Translators,Narrators,DeweyDecimalClassification,UniversalDecimalClassification,LibraryOfCongressClassification,BookImage,Price) Values(@BookName,@Authors,@Publisher,@ISBN13,@ISBN10,@Category,@PublicationDate,@BookLanguage,@Length,@Type,@Translators,@Narrators,@DeweyDecimalClassification,@UniversalDecimalClassification,@LibraryOfCongressClassification,@BookImage,@Price)", OleDbConnect);
            OleDbCommandInsert.Parameters.AddWithValue("@BookName", BookName_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Authors", Authors_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Publisher", PublicationDate_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@ISBN13", ISBN13_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@ISBN10", ISBN10_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Category", Category_ComboBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@PublicationDate", PublicationDate_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@BookLanguage", Language_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Length", ushort.Parse(Length_TextBox.Text));
            OleDbCommandInsert.Parameters.AddWithValue("@Type", Type_ComboBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Translators", Translators_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Narrators", Narrators_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@DeweyDecimalClassification", DeweyDecimalClassification_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@UniversalDecimalClassification", UniversalDecimalClassification_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@LibraryOfCongressClassification", LibraryOfCongressClassification_TextBox.Text);
            OleDbCommandInsert.Parameters.Add(Parameter);
            OleDbCommandInsert.Parameters.AddWithValue("@Price", Price_TextBox.Text);
        });
        OleDbConnect.Open();
        OleDbCommandInsert.ExecuteScalar();
        OleDbConnect.Close();
        Dispatcher.Invoke(() =>
        {
            MW = new MessageWindow();
            System.Threading.Thread.Sleep(3000);
            MW.YesButton.Visibility = Visibility.Hidden;
            MW.NoButton.Visibility = Visibility.Hidden;
            MW.Image.Source = GetImageFromBytes(System.IO.File.ReadAllBytes(System.Windows.Forms.Application.StartupPath + @"\Images\Check.bin"));
            switch (App.EnumLanguage)
            {
                case AllLanguage.English:
                    MW.MessageTextBlock.Text = "Information added successfully";
                    MW.OKButton.Content = "OK";
                    break;
                default:
                    MW.MessageTextBlock.Text = "اطلاعات با موفقیت اضافه شد";
                    MW.OKButton.Content = "تایید";
                    break;
            }
            MW.Show();
        });
    });
}

I use the following tools:
• .NET Framework 4.5
• WPF
• OLE DB
• Windows 7

Thank you for your time.
Merry Christmas

What I have tried:

My general point is that Dispatcher.Invoke shouldn't contain any code that's not directly related to updating the UI,
but sometimes I have to write the code in Dispatcher.Invoke.
Posted
Updated 11-Jan-23 16:00pm
v7
Comments
Graeme_Grant 2-Jan-23 18:37pm    
That is because you are doing all operations on the UI thread. DB queries is a file action that can be made asynchronous, so should be spun off on its own thread. Once you have a response, then you can dispatch back to the UI thread to post the results.
Reza jafery 2-Jan-23 19:05pm    
Hi,
Keep an eye on the following image as it closes in a special mode, which takes two seconds:
Click to see
         <Storyboard x:Key="CloseMessageWindow" Storyboard.TargetName="Message_Window" Completed="CloseMessageWindow_Completed">
             <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].Angle" Duration="0:0:2" To="360"/>
             <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].ScaleX" Duration="0:0:2" To="0"/>
             <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].ScaleY" Duration="0:0:2" To="0"/>
             <DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:2.5" To="0"/>
         </Storyboard>

Now, let's imagine that the message window required 2 seconds to load in a specific state. In which case, we have one of these two problems: if we put the message window in Dispatcher.Invoke, our user interface would be frozen for that time period, and if we put it in await Task.Run, we would run into the following error:
Error: The calling thread must be STA, because many UI components require this.
In light of the aforementioned assumption, what solution do you propose?
Graeme_Grant 2-Jan-23 21:49pm    
Storyboards are asynchronous, so won't cause issues. It is your DB code that is blocking the UI thread. Dispatcher.Invoke is not doing what you think that it is doing, it is for marshaling from a non-UI thread to the UI thread via the SynchronizationContext.
[no name] 2-Jan-23 22:38pm    
Use a BackgroundWorker and update the UI in the "Completed" event. Your "sleeps", DB queries and whatever else are not appropriate as implemented.

I created an algorithm for this specific case. For clearer understanding, I illustrated it as follows:
Click to see
I only inserted the necessary codes.
XAML
<Window x:Class="Ganj_Aseman.MessageWindow" x:Name="Message_Window"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Ganj_Aseman"
        mc:Ignorable="d"
        Loaded="MessageWindow_Loaded" AllowsTransparency="True" Background="Transparent" FontSize="13" Height="228" RenderTransformOrigin="0.5,0.5" ResizeMode="NoResize" ShowInTaskbar="False" Width="430" WindowStartupLocation="CenterOwner" WindowStyle="None">
    <Window.RenderTransform>
        <TransformGroup>
            <RotateTransform/>
            <ScaleTransform/>
        </TransformGroup>
    </Window.RenderTransform>
    <Window.Resources>
        <Storyboard x:Key="CloseMessageWindow" Storyboard.TargetName="Message_Window" Completed="CloseMessageWindow_Completed">
            <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].Angle" Duration="0:0:7" To="360"/>
            <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].ScaleX" Duration="0:0:7" To="0"/>
            <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].ScaleY" Duration="0:0:7" To="0"/>
            <DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:7.5" To="0"/>
        </Storyboard>
    </Window.Resources>
    <Grid>
        <Button Name="OKButton" GotFocus="OKButton_GotFocus" PreviewMouseLeftButtonDown="OKButton_PreviewMouseLeftButtonDown" Content="OK" HorizontalAlignment="Left" VerticalAlignment="Top" Height="34" Margin="177.5,180,0,0">
            <Button.Effect>
                <DropShadowEffect BlurRadius="3" ShadowDepth="0.1" Direction="-90" Color="#e9f1cc"/>
            </Button.Effect>
            <Button.Triggers>
                <EventTrigger RoutedEvent="PreviewMouseLeftButtonDown">
                    <BeginStoryboard Storyboard="{StaticResource CloseMessageWindow}"/>
                </EventTrigger>
            </Button.Triggers>
        </Button>
    </Grid>
</Window>

Owner window:
C#
private async void Add_Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    await Task.Run(() =>
    {
        OleDbParameter Parameter = new OleDbParameter();
        Parameter.OleDbType = OleDbType.Binary;
        Parameter.ParameterName = "BookImage";
        Dispatcher.Invoke(() =>
        {
            Parameter.Value = ImageToBytes((BitmapImage)BookImage.Source);
        });
        OleDbCommand OleDbCommandInsert = new OleDbCommand("Insert Into BookInfo(BookName,Authors,Publisher,[ISBN-13],[ISBN-10],Category,PublicationDate,BookLanguage,Length,[Type],Translators,Narrators,DeweyDecimalClassification,UniversalDecimalClassification,LibraryOfCongressClassification,BookImage,Price) Values(@BookName,@Authors,@Publisher,@ISBN13,@ISBN10,@Category,@PublicationDate,@BookLanguage,@Length,@Type,@Translators,@Narrators,@DeweyDecimalClassification,@UniversalDecimalClassification,@LibraryOfCongressClassification,@BookImage,@Price)", OleDbConnect);
        Dispatcher.Invoke(() =>
        {
            OleDbCommandInsert.Parameters.AddWithValue("@BookName", BookName_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Authors", Authors_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Publisher", PublicationDate_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@ISBN13", ISBN13_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@ISBN10", ISBN10_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Category", Category_ComboBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@PublicationDate", PublicationDate_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@BookLanguage", Language_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Length", ushort.Parse(Length_TextBox.Text));
            OleDbCommandInsert.Parameters.AddWithValue("@Type", Type_ComboBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Translators", Translators_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@Narrators", Narrators_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@DeweyDecimalClassification", DeweyDecimalClassification_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@UniversalDecimalClassification", UniversalDecimalClassification_TextBox.Text);
            OleDbCommandInsert.Parameters.AddWithValue("@LibraryOfCongressClassification", LibraryOfCongressClassification_TextBox.Text);
            OleDbCommandInsert.Parameters.Add(Parameter);
            OleDbCommandInsert.Parameters.AddWithValue("@Price", Price_TextBox.Text);
        });
        OleDbConnect.Open();
        OleDbCommandInsert.ExecuteScalar();
        OleDbConnect.Close();
        Dispatcher.Invoke(() =>
        {
            MessageWindow MW = new MessageWindow();
            MW.Owner = this;
            MW.Owner.IsEnabled = false; //The owner window must be disabled before the message window opens
            MW.YesButton.Visibility = Visibility.Hidden;
            MW.NoButton.Visibility = Visibility.Hidden;
            MW.Image.Source = GetImageFromBytes(System.IO.File.ReadAllBytes(System.Windows.Forms.Application.StartupPath + @"\Images\Check.bin"));
            switch (App.EnumLanguage)
            {
                case AllLanguage.English:
                    MW.MessageTextBlock.Text = "Information added successfully";
                    MW.OKButton.Content = "OK";
                    break;
                default:
                    MW.MessageTextBlock.Text = "اطلاعات با موفقیت اضافه شد";
                    MW.OKButton.Content = "تایید";
                    break;
            }
            MW.Show();
        });
    });
}

Message window:
C#
private void OKButton_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    this.Owner.IsEnabled = true; //The owner window should be enabled as soon as the OK button (in the message window) is pressed
}
private void CloseMessageWindow_Completed(object sender, EventArgs e)
{
    Close();
}

Result (I set the Duration property of the Animation to 7 seconds to better understand the result):
Click to see
 
Share this answer
 
v6
You shouldn't be running your SQL commands in the dispatcher. What Dispatcher.Invoke does is marshall the operation back onto the thread that the Dispatcher is running on, so what you are doing here is actually forcing the "long running" operation back onto the Dispatcher. All you really need to do is marshall the results back to the Dispatcher.

Basically, narrow down the bits that need to be marshalled onto the UI and only use Dispatcher.Invoke for those operations. So, that would not be anything to do with the database in your code sample.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900