1

I have a Primedcommand class within a wpf solution to execute a quick action then execute a task on another thread to prevent the ui thread from being blocked as such:

public void Execute(object parameter)
{
    if (CanExecute(parameter))
    {
        System.Windows.Application.Current.Dispatcher.Invoke(() => { _primer(); });

        Task.Factory.StartNew(() => { _execute(parameter); }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    }
}

And the PrimedCommand constructor:

public PrimedCommand(Action primer, Action<object> execute, Predicate<object> canExecute)
{
    if (primer == null)
        throw new ArgumentNullException("primer");

    if (execute == null)
        throw new ArgumentNullException("execute");

    _primer = primer;

    _execute = execute;
    _canExecute = canExecute;
}

And one of the worker methods at request of @DanPuzey:

Executed action:

private static void AddChoices(ref Settings RunningSettings)
{
    if (Processes.ShouldExit) return;

    try
    {
        if (RunningSettings.Initialized)
        {
            if (Processes.ShouldExit) return;

            if (RunningSettings.WorkingDirectory != null)
            {
                DirectoryInfo workingDir = new DirectoryInfo(RunningSettings.WorkingDirectory);

                if (!workingDir.Exists)
                {
                    throw new DirectoryNotFoundException("The Source Directory Didn't Exist");
                }

                RunningSettings.CurrentStatus.AddMoment(new Moment("Loading Customers"));

                Dictionary<string, string> customerNames = new Dictionary<string, string>();
                Dictionary<string, string> jobNumbers = new Dictionary<string, string>();

                List<DirectoryInfo> availableFolders = new List<DirectoryInfo>();

                if (Tools.IsCustomer(workingDir))
                {
                    availableFolders.Add(workingDir);
                }
                else if (Tools.IsCustomerContainer(workingDir))
                {
                    availableFolders.AddRange(workingDir.EnumerateDirectories().Where(c => Tools.IsCustomer(c)));
                }
                else if (Tools.IsJob(workingDir))
                {
                    availableFolders.Add(workingDir.Parent);
                }

                foreach (DirectoryInfo customer in availableFolders)
                {
                    if (Processes.ShouldExit) return;

                    try
                    {
                        RunningSettings.CurrentStatus.AddMoment(new Moment(String.Format("  Loading Jobs For: {0}", customer)));

                        if (!customerNames.ContainsKey(customer.Name))
                        {
                            customerNames.Add(customer.Name, null); 
                        }

                        foreach (DirectoryInfo job in customer.GetDirectories().Where(j => Tools.IsJob(j)))
                        {
                            if (Processes.ShouldExit) return;

                            try
                            {
                                string tempNumber = job.Name.Substring(0, 6);

                                if (!jobNumbers.ContainsKey(tempNumber))
                                {
                                    jobNumbers.Add(tempNumber, customer.Name); 
                                }
                            }
                            catch (Exception except)
                            {
                                ErrorHandling.Handle(except, ref RunningSettings);
                            }
                        }
                    }
                    catch (Exception excep)
                    {
                        ErrorHandling.Handle(excep, ref RunningSettings);
                    }
                }

                int count = 0;
                int index = 0;

                if (customerNames != null && customerNames.Count > 0)
                {
                    RunningSettings.ClearCustomerCollection();

                    count = customerNames.Count;
                    foreach (KeyValuePair<string, string> customer in customerNames)
                    {
                        if (Processes.ShouldExit) break;

                        try
                        {
                            index++;
                            RunningSettings.AddCustomer(customer.Key, customer.Value, (index == count));
                        }
                        catch (Exception excep)
                        {
                            ErrorHandling.Handle(excep, ref RunningSettings);
                        }
                    }

                    RunningSettings.SortCustomers();
                }

                if (Processes.ShouldExit) return;

                count = 0;
                index = 0;

                if (jobNumbers != null && jobNumbers.Keys.Count > 0)
                {
                    RunningSettings.ClearJobCollection();

                    count = jobNumbers.Count;
                    foreach (KeyValuePair<string, string> job in jobNumbers)
                    {
                        if (Processes.ShouldExit) break;

                        try
                        {
                            index++;
                            RunningSettings.AddJob(job.Key, job.Value, (index == count));
                        }
                        catch (Exception excep)
                        {
                            ErrorHandling.Handle(excep, ref RunningSettings);
                        }
                    }

                    RunningSettings.SortJobs();
                }

                if (Processes.ShouldExit) return;

                RunningSettings.CurrentStatus.AddMoment(new Moment("Loading Customers Complete"));
            }
            else
            {
                throw new InvalidOperationException("The Working Directory Was Null");
            }
        }
        else
        {
            throw new InvalidOperationException("The Settings Must Be Initialized Before Customer Folders Can Be Enumerated");
        }
    }
    catch (Exception ex)
    {
        ErrorHandling.Handle(ex, ref RunningSettings);
    }
}

Cancel action:

public static void Cancel()
{
    KeepRunning = false; // Bool watched by worker processes
}

From the let's call it the execute button the primer's job is to set the value of a property showing the user the action is active.

This is fine, however when I click on the cancel button which will update that status property to cancelling then set a field in the worker class to indicate the action is cancelling, the UI takes about 2 seconds to respond to the button click. I've tried Task.Run and Task.Factory.StartNew with a variety of overloads and creating my own worker thread seems to work the best but still not how I want.

What I'm looking for is you click on execute button, status is updated to active(updating the ui) bound to that property then when the cancel button is clicked the status is changed to cancelling and the task to notify the worker sets the appropriate field(the worker thread checks this field often and exits when needed).

What's not working is the worker thread blocks the ui thread from updating to show the user the action is cancelling.(The cancel button is temporarily unable to be clicked after status is set to active)

Also noteworthy is this solution is using mvvm where the status is an enum value the ui binds to with a converter.

Any question just let me know.

CalebB
  • 597
  • 3
  • 17
  • Use Dispatcher.Invoke to update the UI from another thread. There are tons of examples on SO. Like: http://stackoverflow.com/questions/9602567/how-to-update-ui-from-another-thread-running-in-another-class – David Jul 20 '15 at 17:21
  • @David thank you but I do that already within the property setter. – CalebB Jul 20 '15 at 17:29
  • I did try invoking the action from the dispatcher thread anyways and it had no effect. See the updated code. – CalebB Jul 20 '15 at 17:31
  • "The worker thread blocks the UI thread from updating" - you need to show more code. Somewhere, your code is blocking the UI thread (probably waiting for your worker unintentionally!). – Dan Puzey Jul 20 '15 at 17:33
  • @DanPuzey Do you want code from the worker classes or from the viewmodel delegates invoked by the commands? – CalebB Jul 20 '15 at 17:35
  • I added code from the various worker methods and clarified what I meant by block the ui thread. The changes aren't made then not updated, the command is blocked because the button is temporarily unavailable to be clicked after the first background worker starts. – CalebB Jul 20 '15 at 17:44
  • @CalebB Just to see what happens, try replacing your execute code with Thread.Sleep(10000); You can find out if the problem is in the worker thread or the way you are calling it. – David Jul 21 '15 at 14:37
  • It could be you're just updating a bound variable too quickly and the UI can't keep up. In that case you just need to buffer your updates. – David Jul 21 '15 at 14:45
  • I was thinking that was the case. The program is running another one of it's tasks at the moment but when it finishes I will be sure to try it out to check. In the meantime if that is the case what approach would you recommend using for buffering updates sent to the ui? – CalebB Jul 21 '15 at 14:48

0 Answers0