0

I have been using system.threading for a while now and I trying to wrap my head around tasks. How do you make thread safe calls to a UI control (for example a text box) from another thread using the TPL?

Here is a simple example where I want to update a text box everyone 1 second with the count of my secondary thread.

I have tried a few different methods but I can't seem to get it to work.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        await taskAsync();
    }

    private  Task taskAsync()
    {
        return Task.Factory.StartNew(() => counter());
    }

    private void counter()
    {
        for (int i = 0; i < 10000; i++)
        {
            Task.Delay(1000).Wait();
            textBox1.Text = i.ToString();
        }
    }
}

Is this even possible?

Thank you

user3363744
  • 167
  • 1
  • 9
  • 2
    As a side note, you should use `Task.Run` instead of `StartNew` because [`StartNew` is dangerous](http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html). – Stephen Cleary Mar 25 '16 at 11:54

3 Answers3

3

Well in your current scenario I Think that a Progress would be most suitable. I've made some alterations to your code below:

public partial class Form1 : Form
{
    Progress<int> counterProgress;

    public Form1()
    {
        InitializeComponent();
        counterProgress = new Progress<int>();
        counterProgress.ProgressChanged += CounterProgressUpdated;
    }

    private void CounterProgressUpdated(object sender, int e)
    {
        textBox1.Text = e.ToString();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        await taskAsync(counterProgress);
    }

    private Task taskAsync(IProgress<int> progress)
    {
        return Task.Factory.StartNew(() => counter(progress));
    }

    private async Task counter(IProgress<int> progress)
    {
        for (int i = 0; i < 10000; i++)
        {
            await Task.Delay(1000);
            progress.Report(i);                
        }
    }
}

Since the progress captures the current synchronization context on construction then you should be good to go as long as you create it on the UI thread.

Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed. If there is no current SynchronizationContext at the time of construction, the callbacks will be invoked on the ThreadPool.

Shazi
  • 1,490
  • 1
  • 10
  • 22
1

What about something like this?

private void counter() {
    for (int i = 0; i < 10000; i++) {
        Thread.Sleep(1000); 
            // “There's never an advantage in replacing Thread.Sleep(1000); in Task.Delay(1000).Wait();”
            // http://stackoverflow.com/a/29357131/4267982

        Dispatcher.Invoke(() => { 
            textBox1.Text = i.ToString();
        });
    }
}

Or, as an option, another way without separate thread at all (so there's no need in synchronization):

private async Task taskAsync() {
    for (int i = 0; i < 10000; i++) {
        await Task.Delay(1000);
        textBox1.Text = i.ToString();
    }
}
Surfin Bird
  • 488
  • 7
  • 16
1

How do you make thread safe calls to a UI control (for example a text box) from another thread using the TPL?

You generall do NOT.

If you need to update the UI, then you do that using Invoke. But from TPL directly - you do not want to update the UI too often because this always is a heavy redraw and unless you make a first person shooter frame rate does not matter THAT Much. 10 updates per second are PLENTY.

In this case you may want the tasks to update a central counter (using Interlocked classes) and once that goes above a certain threshhold in changes (or using a timer running in parallel) push that into the UI.

TomTom
  • 61,059
  • 10
  • 88
  • 148