Operate Task in Background AND use Control Elements from another Class

Basically I want to achieve a rather simple matter. I have a C# wpf-Application, where in one class (here in main) I have several control elements. (buttons, textboxes, ...)
Now I execute a method (here the constructor of main). In this method, I want to call another method from a different class (here the method SomeFunction()). Now there are two conditions for this method:

  1. It should work in background, so the GUI is still usable (move around, click buttons, etc.)
  2. It actually needs input from the control elements from the GUI (which is in main)

After searching the internet, my idea for the first requirement was to use a Task.
Actually this works pretty fine, but than I also have to use a Dispatcher, to not run into the error:

System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it

However, by using the Dispatcher, the GUI is blocked again and mot usable anymore.

Is there any way, to fulfill both requirements from top at the same time?

The main class looks something like this:

public class Main
{
    public Main()
    {
        // This class has buttons, textboxes, labels, etc.
        InitializeComponent();

        Task.Run(() => // Task is needed to do work in background (GUI is not blocked)
        {
            Dispatcher.Invoke(() => // Dispatcher is needed to avoid "System.InvalidOperationException"
            {
                // Initialize a new object with this class as parameter
                OtherClass otherObject = new OtherClass(this);

                // Now call a method from another class
                otherObject = new OtherClass(this);
            });
        });
    }
}

And the other class something like this:

public class OtherClass 
{
    public SomeFunction(Main main)
    {
        // Pretty hard work here to do ...
        string test = main.textbox.Text;
    }
}

1 answer

  • answered 2020-03-31 11:21 Phoenix Stoneham

    Try something like this

    public void DoSomething()
        {
            Task.Run(() =>
            {
                BackgroundProcess();
            });
        }
    
        private void BackgroundProcess()
        {
            string ControlValue;
            Application.Current.Dispatcher.Invoke(() =>
            {
                ControlValue = textBox.Text;
            });
            ControlValue += ControlValue;
            Application.Current.Dispatcher.Invoke(() =>
            {
                textBox.Text = ControlValue;
            });
        }
    

    What I'm doing is only accessing the control when I absolutely have to, and doing that in the dispatcher. Everything else is done in the background thread. As long as you're only doing a few entries for this, it shouldn't clog up the dispatcher.

    I've got one place where I do this to log the progress of a process into a textbox using TextBox.AppendText and TextBox.ScrollToEnd, and that doesn't seem to affect the UI. So the key is to do the minimum amount of work necessary using controls. If you're waiting on input, then you should be using MVVM and DataBinding, so that you kick of the relevant parts of the process when the values change.

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Security;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.VisualBasic;
    
    public class MVVMExampleViewModel : System.ComponentModel.INotifyPropertyChanged
    {
        private string _MajorChange;
        public string MajorChange
        {
            get
            {
                return _MajorChange;
            }
            set
            {
                _MajorChange = value;
                DoPropertyChanged("MajorChange");
    -- Start process using value here
            }
        }
    
        private void DoPropertyChanged(string propertyname)
        {
            PropertyChanged(me, New PropertyChangedEventArgs(propertyname));
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    WPF Usage

    <Grid DataContext="{Binding CurrentMVVMExample}" 
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition />
    
                            </Grid.RowDefinitions>
                            <Label Content="{DynamicResource NewItemMessage}" Visibility="{Binding IsDetached, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}" Foreground="#FFBD0000" RenderTransform="{DynamicResource NewItemMessageLocation}"/>
    
                            <Label Content="Status" Grid.Column="0" Grid.Row="1" />
                            <TextBox  Text="{Binding MajorChange, Delay=500, UpdateSourceTrigger=PropertyChanged}" 
                                Grid.Column="1" Grid.Row="1" Width="{DynamicResource SectionFieldWidth}" />
                        </Grid>