-2

I was learning about async/await and I created a little example that didnt work for me. I wanted to change text props in parallel way. Is it possible?

The error says "'Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on".

private async void DeleteAsync()
    {
        var tasks = new List<Task>();
        var list1 = new List<Control>();
        var list2 = new List<Control>();


        list1.Add(textBox1);
        list1.Add(textBox2);

        list2.Add(textBox3);
        list2.Add(textBox4);


        tasks.Add(Task.Run(() => ChangeText(list1)));
        tasks.Add(Task.Run(() => ChangeText(list2)));

        await Task.WhenAll(tasks);

        Console.Write("enddd");
    }


    private void ChangeText(List<Control> lst)
    {            
        foreach (var ctrl in lst)
        {
            ctrl.Text = "22";
        }            
    }

Many thanks!

CABascourt
  • 1,407
  • 2
  • 14
  • 18
  • See marked duplicate. There is extensive discussion there, as well as on any number of other existing questions that involve _"Cross-thread operation not valid"_ exceptions, which will educate you on how thread interactions with UI objects works. – Peter Duniho Apr 10 '19 at 21:51
  • 1
    Reopening; the [duplicate question](https://stackoverflow.com/questions/142003/cross-thread-operation-not-valid-control-accessed-from-a-thread-other-than-the) is over a decade old, and all the highest-voted answers recommend horribly outdated techniques like `Invoke`/`BeginInvoke`. – Stephen Cleary Apr 11 '19 at 01:50

2 Answers2

2

I wanted to change text props in parallel way. Is it possible?

No. But you can marshal as many changes you want to the UI thread, within reason.

One way of doing this is using plain async/await, without using Task.Run. In other words, using asynchrony instead of parallelism.

If you do need parallelism, then one way of updating the UI is to use the IProgress<T>/Progress<T> types to report progress updates. Something like this:

var progress1 = new Progress<string>(update =>
{
  foreach (var ctrl in list1)
    ctrl.Text = update;
});
var progress2 = new Progress<string>(update =>
{
  foreach (var ctrl in list2)
    ctrl.Text = update;
});

tasks.Add(Task.Run(() => ChangeText(progress1)));
tasks.Add(Task.Run(() => ChangeText(progress2)));
await Task.WhenAll(tasks);

...

private void ChangeText(IProgress<string> progress)
{
  progress?.Report("22");
}

One nice benefit of using the IProgress<T> approach is that your processing code is now testable without a UI. I.e., you can write unit tests for it.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

As it stands, there is no need for the Async Await Pattern or Tasks, as all you are doing is updating the UI and the UI being Single Threaded

Though assuming you were doing something of an IO Bound nature that was likely to stall your UI thread (like accessing a database, etc), you could do something like this.

private async Task DoSomethingAsync()
{
    ...

    await DoSomethingAwesomeAsync(list1);
    await DoSomethingAwesomeAsync(list2);

}

...

private async Task ChangeText(List<Control> lst)
{           
    // Awesome IO bound work here

    // await CallDataBaseAsync();

    // await VisitGrandMotherAsync();

    foreach (var ctrl in lst)
    {
        ctrl.Text = "22";
    }            
}

In this case, the current SynchronizationContext will get passed to the IAsyncStateMachine created by the compiler every time you call await and the continuation runs on the calling context (i.e. everything after the await). Which would mean all your UI code is posting back to the UI thread (which will negate your cross-thread exceptions).

halfer
  • 19,824
  • 17
  • 99
  • 186
TheGeneral
  • 79,002
  • 9
  • 103
  • 141