0

I'd like to create a loop thread that runs in parallel from the main thread, so what I did is this code:

ThreadStart rf = delegate () {
    while(true)
    {
         label1_Update();
         Thread.Sleep(10);
    }
};
GlobalThreadsContainer.LabelUpdateThread = new Thread(rf);
GlobalThreadsContainer.LabelUpdateThread.Name = "Thread_paraLoop";
GlobalThreadsContainer.LabelUpdateThread.Start();

I have a static class with values that I want to access to in the previously referred thread. But I get the inter-thread exception. I understand what it is but I could not find a way to access the static value without throwing this exception. Is there a way to do so or am I constrained to use workaround or to even to use a completely different system to create my non-stopping while loop?

to give more information here is how I retrieve my values:

private void label1_Update()
{
    // throws System.InvalidOperationException: inter-thread thingy
    label1.Text = GlobalValues.LastMousePosition.ToString(); 
}

So the idea is to be able to access static values anywhere in my code and especially in parallel threads.

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • 1
    Three very simple example + some notes [here](https://stackoverflow.com/a/62449011/7444103), using `IProgress`, `SynchronizationContext.Post()`, `Control.BeginInvoke()`. All three can be use with either a Task or a Thread, but you should prefer Tasks. – Jimi Oct 11 '20 at 12:43

3 Answers3

1

Take a look at this excellent example on how to work around this limitation of only the main thread being able to update the UI: https://bobnoordam.nl/csharp/update-a-winforms-control-from-a-background-thread/

        /// <summary>
        ///     Start a background thread, which does not block the GUI thread
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonStartTask_Click(object sender, EventArgs e)
        {
            Task t = Task.Factory.StartNew(() => DoWork(_random));
        }
 
        /// <summary>
        ///     In an endless loop, wait a random number of milliseconds and then invoke an update
        ///     of the control on the GUI thread.
        /// </summary>
        /// <param name="random"></param>
        private void DoWork(Random random)
        {
            while (true)
            {
                int result = random.Next(1000);
                Thread.Sleep(result);
                Invoke(new Action<int>(UpdateGuiText), result);
            }
        }
 
        /// <summary>
        ///     Update the control on the GUI thread. (Called from the background thread)
        /// </summary>
        /// <param name="result"></param>
        private void UpdateGuiText(int result)
        {
            textBoxResults.Text = result.ToString();
        }
    }
}

In your case rf is the DoWork and the UpdateGuiText is your label1_Update

Athanasios Kataras
  • 25,191
  • 4
  • 32
  • 61
  • @TheodorZoulias What do you suggest instead of `Control.Invoke`? – Emperor Orionii Oct 11 '20 at 19:14
  • @EmperorOrionii for the specific problem a simple [`Timer`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.timer) should be sufficient. Or alternatively an infinite loop using `await Task.Delay`, like in [this](https://stackoverflow.com/questions/22872589/how-to-cancel-await-task-delay) question. If I had to call the `GlobalValues.LastMousePosition` from a background thread for some reason, I would offload this call by using `Task.Run` like in [this](https://stackoverflow.com/questions/64003533/ui-unresponsive-until-action-is-complete) question. – Theodor Zoulias Oct 11 '20 at 19:24
1

Control objects like Form and Label have InvokeRequired property for checking if you are on the right thread to update UI and BeginInvoke and Invoke for executing given delegate on UI thread. Those methods pack a delegate in a special message type, send it to UI's message loop where UI thread handles it like any other event (mouse move or button press), except you don't have to write message handler. Immediate solution to you problem would to just wrap method body with BeginInvoke:

private void label1_Update()
{
    label1.BeginInvoke(new Action<string>(text => label1.Text = text), GlobalValues.LastMousePosition.ToString());
}

But more robust solution for more complex UI updates is to put following preamble to a method:

private void label1_Update()
{
    if (label1.InvokeRequired)
    {
        label1.BeginInvoke(new Action(label1_Update));
        return;
    }

    label1.Text = GlobalValues.LastMousePosition.ToString(); 
}

This basically checks if you are on the correct thread and if not, the whole method is sent to UI thread.

The difference between BeginInvoke and Invoke is that BeginInvoke is non-blocking while Invoke blocks until UI finishes with running the delegate. Optionally BeginInvoke can have matching EndInvoke for blocking until delegate finishes and getting a return value or exceptions.

Emperor Orionii
  • 703
  • 10
  • 22
-1

You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive.Windows.Forms (assuming that this is Windows Forms) and add using System.Reactive.Linq; - then you can do this:

IDisposable subscription =
    Observable
        .Interval(TimeSpan.FromMilliseconds(10.0))
        .ObserveOn(this)
        .Subscribe(_ => label1.Text = GlobalValues.LastMousePosition.ToString());

That's it. All of the timing and marshalling to the UI thread is handled.

Just call subscription.Dispose() to stop the subscription.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172