7

I am new to Async and Await and have created a simple project in order to understand how it works. For this, I have a simple Windows Form application that has 2 elements:

  • Get Completed Items button
  • TextBox showing all Completed Items retrieved

When I click the button, it should display all completed Items in the TextBox. This is the code I have written:

private async void btnGetCompletedItems_Click(object sender, EventArgs e)
{
    QueueSystem queueSystem = QueueSystem.NewInstance(75);

    Stopwatch watch = new Stopwatch();

    watch.Start();
    await Task.Run(() => GetCompletedItems(queueSystem));
    watch.Stop();

    lblTime.Text = $"{watch.ElapsedMilliseconds.ToString()} ms";

}

private void GetCompletedItems(QueueSystem queueSystem)
{
    foreach (var item in queueSystem.GetCompletedItems())
    {
        txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
    }
}

However, I am getting an error in

txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";

The error says

Additional information: Cross-thread operation not valid: Control 'txtItems' accessed from a thread other than the thread it was created on.

I checked in Debug and a new thread was created for GetCompletedItems(). When I read about Async and Await, I read that it doesn't necessarily create a new thread but it seems to have created a new one for some reason.

Is my implementation and understanding of Async and Await wrong? Is it possible to use Async and Await in a Windows Forms application?

thecodeexplorer
  • 363
  • 1
  • 6
  • 18
  • Possible duplicate of [Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on](https://stackoverflow.com/questions/142003/cross-thread-operation-not-valid-control-accessed-from-a-thread-other-than-the) –  Nov 23 '18 at 04:30
  • `Task.Run` involves other threads, without going into too much detail on which threads and why. – Lasse V. Karlsen Nov 23 '18 at 07:38

3 Answers3

8

You cannot access UI thread on a different thread. This should help

private async void btnGetCompletedItems_Click(object sender, EventArgs e)
{
    QueueSystem queueSystem = QueueSystem.NewInstance(75);

    Stopwatch watch = new Stopwatch();

    watch.Start();
    var results = await Task.Run(() => queueSystem.GetCompletedItems());
    foreach (var item in results)
    {
        txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
    }
    watch.Stop();

    lblTime.Text = $"{watch.ElapsedMilliseconds.ToString()} ms";

}
Elwick
  • 126
  • 1
  • 7
2

You can access the thread from another thread in a following way. It does helps to avoid the cross thread exception in your application.

private void Thread()
{
    this.Invoke((System.Action)(() => {
        //your thread call or definition
    });
}
Ajar
  • 162
  • 1
  • 10
2

When I read about Async and Await, I read that it doesn't necessarily create a new thread

This is true for regular async methods. Consider this:

    private async void button1_Click(object sender, EventArgs e)
    {
        Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);

        await DoesNothing();
    }

    private async Task DoesNothing()
    {
        // outputs the same thread id as similar line as from above;
        // in particlar, for WinForms this means, that at this point
        // we are still at UI thread
        Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);

        await Task.Delay(1);
    }

but it seems to have created a new one for some reason

This is what Task.Run is intended for:

Queues the specified work to run on the ThreadPool

In other words, it pushes anything you pass it as a delegate to a thread pool thread. Since we are in WinForms, this means, that anonymous method () => GetCompletedItems(queueSystem) will be executed at thread pool thread, not at UI one.

Here's code sample from above with little change:

    private async void button1_Click(object sender, EventArgs e)
    {
        Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);

        await Task.Run(DoesNothing);
    }

    private async Task DoesNothing()
    {
        // outputs DIFFERENT thread id;
        // in particlar, for WinForms this means, that at this point
        // we are not at UI thread, and we CANNOT access controls directly
        Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);

        await Task.Delay(1);
    }
Dennis
  • 37,026
  • 10
  • 82
  • 150