2

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.

1
  • Most of the explanation and working sample available in stackoverflow.com/questions/17972268/… (almost duplicate). The exact problem (creating Progress before synchronization context created) answered by Servy (also there are many similar questions there is no good duplicate target yet). Commented Sep 10, 2015 at 14:23

2 Answers 2

8

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Does that mean a SynchronizationContext is created upon making a form?
@Ringil No, it's created when you start the message loop. Generally you should be using Application.Run to start your application loops, although you're using ShowDialog instead.
Well, I was trying to make a simple example to debug this issue in another problem. That other program uses ShowDialog, but I suppose that does not matter as that also creates a message loop.
0

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.

9 Comments

Isn't Async-Await supposed to mean that you don't have to do Invokes?
@Ringil Yes, it is. Sadly too many people, including this answerer, don't actually understand that and are stuck in the past, and insist on using extremely outdated and inferior tools for this task.
This isn't explaining why the second snippet works perfectly fine (as that is how these tools are supposed to be used; this answer is not.) The whole point of using Progress is so that you don't have to explicitly invoke.
@MichałKędrzyński await most certainly has something to do with the dispatcher. When you await something the current synchronization context is captured, and in a WPF application that'll be a wrapper for the Dispatcher, which will result in the continuation being posted to that sync context, which will post it to the dispatcher. There is no need for a custom awaiter to do any of this. Also note that the OP appears to be using a winform app, not a WPF app, so there is no dispatcher. Fortunately await and Progress aren't dependent on such details, unlike your code.
@vidalsasoon It's all going to depend on the specifics of the situation, but in the vast majority of cases, no. If you have an API that represents an asynchronous operation that doesn't provide a Task, leaving you with an event/callback/etc. then you'd want to first transform it into a Task (how would depend on what, specifically, it is doing) so that you can then await it from the UI thread. In certain very unlikely situations doing so may not be practical, and so you'd want to capture the UI's synchronization context and use that to start a new thread that will run in the UI context.
|

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.