2
\$\begingroup\$

I'm trying to figure out how to nicely write a Winforms app that reports the progress of completing a long task onto the main form's controls without causing Cross-thread op exceptions and such. So far I've come up with this scheme which at least works, but doesn't look very nice:

Progress<MyProgress> progress = new Progress<MyProgress>();
private void Form1_Load(object sender, EventArgs e) 
{
    progress.ProgressChanged += (x, y) =>
        Invoke(new Action(() => { toolStripStatusLabel1.Text = y.text;
                                  toolStripProgressBar1.Value = y.percentage; }));
}

bool DoVeryHeavyStuff(IProgress<MyProgress> progress)
{
    progress.Report(new MyProgress() { percentage = 0, text = "Doing heavy stuff…" });
}

async void button_doHeavyStuff_Click(object sender, EventArgs e)
{
    bool success = await Task.Run(() => DoVeryHeavyStuff(progress));
}

As you can see, the right part of the progress.ProgressChanged += looks overly complex. Isn't there a way it can be written more simply?

One thing I could think of is creating a separate method, but that wouldn't help much, as it would still have to be referred to in that line.

progress.ProgressChanged += (x, y) =>
    Invoke(new Action(() => progress_ProgressChanged(x, y)));

void progress_ProgressChanged(object sender, MyProgress e)
    {
        toolStripStatusLabel1.Text = e.text;
        toolStripProgressBar1.Value = e.percentage;
    }

I managed to break it apart like this, but it looks even worse:

progress.ProgressChanged += (x, y) => ReportProgressA(x, y);
void ReportProgressA(object sender, MyProgress e)
{
    Invoke(ReportProgressB(sender, e));
}
Action ReportProgressB(object sender, MyProgress e)
{
    return () => progress_ProgressChanged(sender, e);
}

Invoke seems to be essential to avoiding cross-thread exceptions, but it requires a delegate as a parameter. I don't have much experience with delegates, so I don't know how to simplify the Action initialization code.

It looks like I can't just drop the explicit cast to Action for some reason: this is why we can't have nice things

\$\endgroup\$
  • 2
    \$\begingroup\$ Ah, had forgotten that Invoke takes a generic delegate and isn't just expecting an Action. Answer deleted as it's incorrect. More info as to why: stackoverflow.com/questions/9549358/… \$\endgroup\$ – Nick Udell Oct 2 '14 at 9:04
3
\$\begingroup\$

What you've provided is a lambda expression.

The Invoke method takes a delegate as an argument. Before I explain what a delegate is, the simple answer to your question is provide one of the predefined delegate types Action or Func. These may seem like magic, but trust me, they are super simple concepts once you wrap your head around them.

Invoke(new Action(() => {...})) Action takes no parameters and returns void

Invoke(new Func<bool>(() => { return false; })); Func<T> returns T and has no parameters.

Action and Func<T,...> are simply predefined delegates. Action is defined like so:

public delegate void Action();

What is a delegate?

A delegate plain and simple is a way to describe a method signature - that is, what does the method return (if anything) and what arguments (if any) does it take.

Another way to think about this is when I'm done doing my work I always notify someone to let them know the status of goals achieved. To make this process flexible I allow you to specify that unit of work. I will invoke the custom method and send it the status of my work. For this to happen obviously I need to set some general ground rules, the rules being that the method I invoke needs to take a string (the status I send you) and return a response to me (maybe that response is "ok go home" or etc).

The beauty of specifying only the method signature is that you can make your own custom method to be invoked (as you want to above) or you could specify any method that matches the signature. In our case string in, string out would match String.Reverse (just the name). Obviously in our scenario I may be confused by the response, but this way of programming allows you to plug in different implementations. One implementation could be that I notify you via phone call, or email, or text message etc.

Depending on your use case you might return void from the method because you just want an email to be kicked off once I internally invoke the method you've given to me.

You see a lot of this programming in thing like JavaScript, it is asynchronous, it returns control back to you right away and allows you to specify a callback function, essentially a delegate that should be invoked once it's done.

|improve this answer|||||
\$\endgroup\$
0
\$\begingroup\$

On your window or base window class, implement a method like this:

    protected virtual void InvokeIfRequired(Action action)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(action);
        }
        else
        {
            action();
        }
    } 

Then when you want to update a GUI element, do this:

    this.InvokeIfRequired(() => textBox.Text = "Some result");
    // or a block
    this.InvoveIfRequired(() => {
        textBoxt2.Text = "Some result 2";
        textBoxt3.Text = "Some result 3";
    });
|improve this answer|||||
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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