1

I'm trying to make a progress bar for my project but while the work is being done they shouldn't be able to interact with the main window (this is why it needs to be ShowDialog). So I've came with this solution but due to having zero multi-threading experience I don't know if this is good solution.

This is just some model code to represent the work that needs to be done and works like it's meant to.

private async void Button_Click(object sender, RoutedEventArgs e)
{
    Window1 progress_Bar = new Window1();
    Thread test = new Thread(() => test_method(progress_Bar));
    test.Start();
    for (int i = 0; i < 11; i++)
    {
        //work
        progress_Bar.Bar.Value = i;
        await Task.Delay(1000);
    }
    progress_Bar.Close();
    test.Abort();
}

private void test_method(Window1 _Test)
{
    _Test.Dispatcher.Invoke(() =>
    {
        _Test.ShowDialog();
    });
}

Where: Window1 is the progress bar window form I'm opening and the user sees while the work is being done and .Bar is the progress bar.

So my main question, is this using threads in a secure a proper way to do asynchronous tasking?

Andrew Foot
  • 119
  • 10

2 Answers2

2

I'd look into the IProgress T interface.

Create a class that will hold your progress information like progress value, max, min if needed..

public class MyProgressReport
{
    public int ProgressValue{ get; set; }
}

And a ReportProgress method somewhere in your class which will take progress reports and update info on the UI

private void ReportProgress(MyProgressReport progressReport)
{
    //your dialog window needs a property to handle progress
    //or just access its Bar.Value like you do in your example
    dialogWindow.Progress = progressReport.ProgressValue;
}

And finally a method for showing the dialog / doing work. Tell it what you want to do with an Action.

public async void ShowBusyWindow(Action<IProgress<MyProgressReport>> operation)
{
    //ReportProgress method will be called every time you want to update progress
    var progressCallback = new Progress<MyProgressReport>(ReportProgress);

    progressWindow = new Window();

    var workTask = DoWork(operation(progressCallback));
    progressWindow.ShowDialog(); //program will wait here until window is closed
    await workTask; // usually not needed since workTask will be done by the time this is hit, this is where exceptions will be thrown
}

private async Task DoWork(Action operation)
{
    //you should also check for exceptions here and close window
    await Task.Run(operation);
    //close window when done
    progressWindow.Close();
}

Finally, to use it, you would just call Show like so:

ShowBusyWindow((progress) => 
{
    //do work here and update progress
    var report = new MyProgressReport();

    for(int i=0; i<100; i++)
    {
        report.ProgressValue = i;
        progress.Report(report);
        Thread.Sleep(500);
    }
});

I haven't run this (mistakes may be present) but I use an almost identical approach with some more properties on the progress report class and exception handling.

  • Thanks and very easy to follow! – Andrew Foot Nov 13 '19 at 13:35
  • 1
    The `MyProgressReport` class is not really needed IMHO. A `Progress` should be enough. – Theodor Zoulias Jun 05 '20 at 06:32
  • 1
    @TheodorZoulias Indeed it is not. It does allow for more data to be passed through it though. You could add a string property to indicate to the dialog window what your code is currently doing like say "now downloading some data..", "now cleaning up..." etc. For this example, you are absolutely correct, a generic Progress is enough. – pizza_coder Jun 05 '20 at 18:25
1

You need an async ShowDialog method for that

public static class WindowExtensions
{
    public static async Task<bool?> ShowDialogAsync(this Window window)
    {
        await Task.Yield(); // this is the magic ;o)
        return window.ShowDialog();
    }
}

and now you can do

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private async void Button_Click( object sender, RoutedEventArgs e )
    {
        var dialog = new WaitWindow();
        var task = WorkAsync( dialog.Progress );
        var dialogTask = dialog.ShowDialogAsync();
        await task;
        dialog.Close();
        await dialogTask;
    }

    private async Task WorkAsync( IProgress<double> progress )
    {
        for ( int i = 0; i < 100; i++ )
        {
            progress.Report( i );
            await Task.Delay( 25 ).ConfigureAwait( false );
        }
    }
}

the WaitWindow class

public partial class WaitWindow : Window
{
    public WaitWindow()
    {
        InitializeComponent();

        Progress = new Progress<double>( progress => ProgressHandler( progress ) );
    }

    public IProgress<double> Progress { get; }

    private void ProgressHandler( double progress )
    {
        progressBar.Value = progress;
    }
}
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73