0

I have an application built in C# using the MVVM pattern that will have 40-50 different tests that need to be run. I want to show each test as a list item with a progress bar of how far each test has progressed. I want to run multiple tests at one time and at some point in the future there may be dependencies where certain tests have to run before other tests run. I'm trying to figure out how to set up the threads to manage these requirements. I'm aware of the ThreadPool, but I'm not sure it will work for my requirements. What are the limitations of Thread Pool and is there a better way to go about building these requirements?

1
  • Start building app with a ThreadPool and then when you get some performance issues, you can consider "optimisation" with more knowledge about your software.
    – Fabio
    Apr 27, 2017 at 21:10

1 Answer 1

2

Firstly - I'm assuming that the thread running your test has some mechanism which can notify on its own progress somehow (maybe with an event or delegate?)

In the spirit of MVVM, consider a Model which represents the state and progress of your test which you can be updated by the test thread.

The main purpose of the Model would be to remember the state of the UI progress bar (e.g. an int for a percentage). For example:

public class TestModel : INotifyPropertyChanged
{
    private int _progressPercentage;
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public int ProgressPercentage
    {
        get { return _progressPercentage; }
        set
        {
            OnPropertyChanged();
            _progressPercentage = value;
        }
    }
}

With the MVVM-based UI frameworks (such as Silverlight, WPF, etc.) you need a way to ensure that any updates affecting the UI state (and therefore the properties on the Model) are invoked using the Dispatcher thread (the main/UI thread).

(Related: https://stackoverflow.com/questions/4621623/wpf-multithreading-ui-dispatcher-in-mvvm)

You may have as many ThreadPool threads as you like, but those threads will need to invoke UI state changes on the Dispatcher thread when you're changing any properties on the Model, since INotifyPropertyChanged causes the state of the UI to change.

Also, When dealing with multiple components of the same time, an ObservableCollection<> is a typical choice; where each model could bind to a UI Progress Bar component (e.g. a <ListBox>). Again, any kind of modifications such as Add or Remove needs to happen on the Dispatcher thread.

Here's a possible example for a ViewModel where the action of starting a test thread originates from an ICommand (button click perhaps?)

public class MyTestViewModel
{
    public ObservableCollection<TestModel> AllRunningTests { get; set; } =
        new ObservableCollection<TestModel>();
    public ICommand RunTestCommand { get; set; }

    public MyTestViewModel()
    {
        RunTestCommand = new DelegateCommand<object>(RunTest, _ => true);
    }

    private void RunTest(object parameter)
    {
        var testModel = new TestModel();
        AllRunningTests.Add(testModel);
        Task.Run(() =>
        {
            while (testModel.ProgressPercentage < 100)
            {
                Thread.Sleep(200); // Simulate an expensive process with a sleep
                Dispatcher.Invoke(() => { testModel.ProgressPercentage += 5; });
            }
        });
    }
}

Note that Task.Run executes its action on a threadpool thread - More info on Stephen Cleary's blog - Creating Tasks


None of the above code really takes advantage of the full capabilities async/await or Task.

Among the reasons to use Task instead of starting a threadpool thread directly include:

  • User cancellation of a running thread (CancellationTokenSource)
  • Timeout (Task.WhenAny and Task.Delay) )
  • Simplified exception handling
  • Returning values back from a thread (Task.TrySetResult).
  • Checking thread completion or failure status

For these things you might find Stephen's AsyncCommand to be useful - Async Programming : Patterns for Asynchronous MVVM Applications: Commands

The AsyncCommand may help simplify the task of 'chaining' multiple dependent tests. For example, a list of TestModel objects.

This snippet assumes that RunTestsAsync happens inside an AsyncCommand, each Task is executed as the previous task completes, but the async/await specifiers prevent the Dispatcher thread from being blocked while each Task awaits completion:

private async Task RunTestsAsync(object parameter)
{
    var testQueue = GetSomeListOfTests(); 

    foreach (var testModel in testQueue)
    {
        AllRunningTests.Add(testModel); 

        await Task.Run(() => 
        {
            while (testModel.ProgressPercentage < 100)
            {
                Thread.Sleep(200); 
                Dispatcher.Invoke(() => testModel.ProgressPercentage += 5);
            }
        });
    }
}

Note: Code snippets above aren't tested or checked in a compiler, but the concepts are more important than the actual code.

The Task, async and await features of C# can be a little mindblowing the first time you see them. Some extra reading is highly recommended, as well as trying examples in a test app to get comfortable with how they work in practice.

The most important thing to realise is that while a traditional Thread.Wait will block the UI thread, the await keyword will not block the UI thread, and is a very elegant way of handling long-running tasks inside a Button click command or other GUI event handler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.