windowsgm
windowsgm

Reputation: 1616

Updating Progressbar from Two Tasks

I'm trying to update a progress bar from two separate tasks at the same time. I've tried it a number of ways but haven't had much success. See below full code example. In WPF, add a progressbar, bind Maximum to TextMax and Value to TextProgress and you will notice that the progress bar only fills to about half way.

NOTE: This is not my actual solution, just a sample of what I am doing that shows the issue that I threw together, please ignore code style/pattern issues.

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace DelegateTesting
{
    public partial class MainWindow : Window
    {
        ResultsItemViewModel _ViewModel = new ResultsItemViewModel();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = _ViewModel;
            TextProcessing();
        }
        private static void Method1(
            Action<int> reportProgress)
        {
            var progress = 0;

            for(int i = 0;i<100;i++)
            {
                //Thread.Sleep(200);
                reportProgress?.Invoke(++progress);
            }
        }
        private static void Method2(
            Action<int> reportProgress)
        {
            var progress = 0;

            for (int i = 0; i < 100; i++)
            {
                //Thread.Sleep(200);
                reportProgress?.Invoke(++progress);
            }
        }
        private async Task TextProcessing()
        {
            _ViewModel.TextMax += 100;
            _ViewModel.TextMax += 100;

            var dispatcher = Application.Current.Dispatcher;
            var reportProgress = dispatcher.MakeInvoker<int>(p => _ViewModel.TextProgress = p);

            await Task.WhenAll(
                Task.Run(() => Method1(reportProgress)),
                Task.Run(() => Method2(reportProgress)));
        }
    }
    public static class DispatcherHelper
    {
        public static Action<T> MakeInvoker<T>(
            this Dispatcher dispatcher,
            Action<T> action,
            DispatcherPriority priority = DispatcherPriority.Normal)
        {
            return o => dispatcher.BeginInvoke(priority, action, o);
        }
    }
    public class ResultsItemViewModel : INotifyPropertyChanged
    {
        int _textProgress, _textMax;
        public int TextProgress
        {
            get => _textProgress;
            set
            {
                _textProgress = value;
                NotifyPropertyChanged();
            }
        }

        public int TextMax
        {
            get => _textMax;
            set
            {
                _textMax = value;
                NotifyPropertyChanged();
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Upvotes: 0

Views: 826

Answers (1)

FCin
FCin

Reputation: 3915

You are not awaiting TextProcessing. You cannot just call await in the middle. You can either go asynchronous all the way or not at all. Also, doing work in a constructor is not a good practice.

In order for this to work you have to allow WPF to handle async calls for you. I assume you want to start an action when someone does something, e.g.:

protected async void Button_OnClick(Object sender, EventArgs e)
{
    await TextProcessing();
}

You could always bind it to an event that starts a window or something like that.

To be honest your code is very unclear to me, so this might help you understand what you actually need:

   int _textProgress, _textMax;
    public int TextProgress
    {
        get => _textProgress;
        set
        {
            _textProgress = value;
            NotifyPropertyChanged();
        }
    }

    public int TextMax
    {
        get => _textMax;
        set
        {
            _textMax = value;
            NotifyPropertyChanged();
        }
    }
 protected async void Button_OnClick(Object sender, EventArgs e)
    {
        TextMax = 0;

        var t1 = Task.Run(() => {
            TextProgress += 50;
        });
        var t2 = Task.Run(() => {
            TextProgress += 50;
        });

        await Task.WhenAll(t1, t2);
    }

And in your view you should have some kind of button with command for Button_OnClick and progress bar:

<ProgressBar Maximum="100" Height="50" Value="{Binding TextProgress}"></ProgressBar>

One more thing. It looks like you have a ViewModel, but you are doing work in a view. You should move that logic to your ViewModel.

Upvotes: 1

Related Questions