Ringil
Ringil

Reputation: 6527

Async-Await Progress Bar and Cross Threading Issue

I've been working on a progress bar with async and await in C#. I made a simple form Form1.cs with the following code:

public partial class Form1 : Form
{
    private Progress<TaskAsyncExProgress> progress;
    public Form1(Progress<TaskAsyncExProgress> progress = null)
    {
        InitializeComponent();
        if (progress == null)
        {
            this.progress = new Progress<TaskAsyncExProgress>();
        }
        else
        {
            this.progress = progress;
        }

        this.progress.ProgressChanged += (s, e) =>
        {
            Debug.WriteLine("Progress: " + e.ProgressPercentage + "%");

            progressBar.Value += e.ProgressPercentage;

            txtResult.Text += e.Text;
        };

        Shown += async (s, e) =>
        {
            try
            {
                await Task.Run(() => HardTask(progress));
                txtResult.Text += "Done!";
            }
            finally
            {
                MessageBox.Show("Done!");
                Close();
            }
        };
    }

    void HardTask(Progress<TaskAsyncExProgress> progress)
    {
        incrementProgress();
        Thread.Sleep(5000);
    }
    public void incrementProgress()
    {
        Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                var args = new TaskAsyncExProgress();
                args.ProgressPercentage = 1;
                args.Text = i.ToString() + " "; 
                ((IProgress<TaskAsyncExProgress>)progress).Report(args);
                Thread.Sleep(10);
            }
        }
        );
    }
}


public class TaskAsyncExProgress
{
    public int ProgressPercentage { get; set; }

    public string Text { get; set; }
}

In my Program.cs, I have two cases:

// Case 1: This causes a cross-thread issue
//Progress<TaskAsyncExProgress> progress = new Progress<TaskAsyncExProgress>();
//Form1 frm =  new Form1(progress);

// Case 2: This works fine
Form1 frm =  new Form1();

frm.ShowDialog();

Why is it that Case 1 causes a cross-threading issue, but Case 2 works fine? I feel like Case 1 could be a pretty useful construct if you want to use the Report method outside of the form itself.

Edit: I know that I can use a BeginInvoke or similar, but I was under the impression that Async+Await were supposed to make it so that I don't need to do that anymore.

Upvotes: 2

Views: 991

Answers (2)

Servy
Servy

Reputation: 203821

The Progress class captures the value of SynchronizationContext.Current when it's created, and uses that (or the default sync context if there is no value) to post the progress changed event handlers.

In your first example you're creating the Progress instance before the synchronization context is created. In the second example you're creating it after the sync context is created, which is why it works.

Upvotes: 8

Kędrzu
Kędrzu

Reputation: 2425

Updates made to the UI must take place in UI thread. Consider this:

this.progress.ProgressChanged += (s, e) =>
    {
        Dispatcher.BeginInovoke(() => {
            Debug.WriteLine("Progress: " + e.ProgressPercentage + "%");

            progressBar.Value += e.ProgressPercentage;

            txtResult.Text += e.Text;
        });
    };

EDIT: This applies to all UI updates, not only in this particural event.

Upvotes: 0

Related Questions