The problem with threads where mono freezes and MS.Net does not

I am testing my application with mono in anticipation of the Linux port, and I have a threading issue. At first I looked at inserting 3,000 lines of code, but finally I developed a small minimal example;)

You have a form with a button (poetically named Button1 ) and a shortcut (which, without surprise, has the name Label1 )). A whole life is a happy life on a form called Form1 . Clicking Button1 starts an infinite loop that increments the local counter and updates Label1 (using Invoke ) to reflect its value.

Now in Mono, if you change the size of the form, the label stops updating, not restarting. This does not happen with the implementation of MS. BeginInvoke does not work better; Worse, in both cases, the user interface freezes.

Do you know where this discrepancy comes from? How would you decide? And finally, why is BeginInvoke not working here? I have to make a huge mistake ... but what?


EDIT : There is still some progress:
  • The BeginInvoke call really works; only the user interface just doesn't update fast enough, so it seems to stop.
  • In mono, what happens is that the whole thread hangs when you insert a message into the UI queue (for example, changing the size of the form). In fact, the synchronous Invoke call never returns. I am trying to understand why.
  • Interesting: even when using BeginInvoke asynchronous calls are not made until the resize operation completes. In MS.Net, they continue to work when resized.

The code is as follows (C # version below):

 Public Class Form1 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim T As New Threading.Thread(AddressOf Increment) T.Start() End Sub Sub UpdateLabel(ByVal Text As String) Label1.Text = Text End Sub Delegate Sub UpdateLabelHandler(ByVal Text As String) Sub Increment() Dim i As Long = 0 Dim UpdateLabelDelegate As New UpdateLabelHandler(AddressOf UpdateLabel) Try While True i = (i + 1) Mod (Long.MaxValue - 1) Me.Invoke(UpdateLabelDelegate, New Object() {i.ToString}) End While Catch Ex As ObjectDisposedException End Try End Sub End Class 

Or, in C #,

 public class Form1 { private void Button1_Click(System.Object sender, System.EventArgs e) { System.Threading.Thread T = new System.Threading.Thread(Increment); T.Start(); } public void UpdateLabel(string Text) { Label1.Text = Text; } public delegate void UpdateLabelHandler(string Text); public void Increment() { long i = 0; UpdateLabelHandler UpdateLabelDelegate = new UpdateLabelHandler(UpdateLabel); try { while (true) { i = (i + 1) % (long.MaxValue - 1); this.Invoke(UpdateLabelDelegate, new object[] { i.ToString() }); } } catch (ObjectDisposedException Ex) { } } } 
+6
source share
3 answers

This is a bug in the mono executable version, at least I think it is. The code may not be good practice (I'm not a thread expert), but what the error says is that the behavior is different from Windows and Linux.

On Linux, mono has the same behavior as MS.Net for Windows. Lack of continuous updates even when resizing.

In Windows, mono displays all of the above problems. I posted a bug report at https://bugzilla.novell.com/show_bug.cgi?id=690400 .

+5
source

Do you know where this discrepancy comes from? How would you allow it?

I'm not sure. I do not see anything obvious in your code that will make the difference between Mono and .NET. If I had to make wild assumptions, I would say that there is a possibility that you stumbled upon an incomprehensible mistake in Mono. Although, I believe, it is possible that Mono uses a rather different mechanism for processing WM_PAINT messages, which cause the form to be updated. Constantly stressing the user interface flow from callbacks to Invoke can disrupt Mono's ability to update the form.

And finally, why doesn't BeginInvoke work here?

Invoke in a tight loop is bad enough, but BeginInvoke will be even worse. A workflow floods the user interface message pump. BeginInvoke did not BeginInvoke for the user delegate thread to finish executing. It simply sends requests and returns them quickly. That is why it seems to be hanging. Messages sent by BeginInvoke to the UI message queue continue to grow, as the workflow is likely to severely disrupt the user interface thread by processing them.

Other comments

I should also mention that the workflow is almost useless in the code. The reason is that at each iteration you invoke Invoke . Invoke blocks until the user interface completes the delegate. This means that your workflow and user interface thread are essentially in blocking mode with each other. In other words, the worker spends most of his time waiting for the interface and vice versa.

Decision

One possible fix is ​​to slow down the speed at which Invoke is called. Instead of calling it at each iteration of the loop, try doing it every 1000 iterations or the like.

Any even better approach is to not use Invoke or BeginInvoke at all. Personally, I believe that these mechanisms for updating the user interface are used excessively. It is almost always better for the user interface thread to throttle its own update rate, especially when the worker thread is performing continuous processing. This means that you will need to put a timer on the form and mark the desired refresh rate. From the Tick event, you examine the general data structure that the workflow updates, and use this information to update the controls on the form. This has several advantages.

  • It breaks the tight connection between the user interface and the workflows that Control.Invoke imposes.
  • He is responsible for updating the user interface thread in the user interface thread, where he must belong in any case.
  • The user interface flow determines when and how often an update should occur.
  • There is no risk of user interface message overflow, as would be the case with marshaling methods initiated by a workflow.
  • The workflow should not wait for confirmation that the update has been completed before proceeding with the next steps (i.e. you will get more bandwidth for both the user interface and workflows).
+1
source

First of all: pressing Button1 is already asynchronous, so you do not need to create another thread to increase, just call the increment method Sorry, I was reading your question in turn, and by the time I got to the while loop, I forgot about the button :

 private void Button1_Click(System.Object sender, System.EventArgs e) { Thread t = new Thread(Increment); t.IsBackground = true; t.Start(); } 

Secondly: if you need to use a thread, you should always indicate your thread in the background (i.e. the foreground prevents the process from completing ), unless you have a good reason to use the front thread.

Third: if you are making updates to the user interface, then you should check the InvokeRequired property and call BeginInvoke :

 public void UpdateLabel(string Text) { if (InvokeRequired) { BeginInvoke(new UpdateLabelDelegate(UpdateLabel), Text); } else { Label1.Text = Text; } } public void Increment() { int i = 0; while(true) { i++; // just incrementing i?? UpdateLabel(i.ToString()); Thread.Sleep(1000);// slow down a bit so you can see the updates } } 

You can also "automate" Invoke Required "pattern": Automate InvokeRequired code

Now look if you have the same problem.

I tried this on my machine and it works like a charm:

 public partial class Form1 : Form { private delegate void UpdateLabelDelegate(string text); public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(Increment); t.IsBackground = true; t.Start(); } private void UpdateLabel(string text) { if (label1.InvokeRequired) { BeginInvoke(new UpdateLabelDelegate(UpdateLabel), text); } else { label1.Text = text; } } private void Increment() { int i = 0; while (true) { i++; UpdateLabel(i.ToString()); Thread.Sleep(1000); } } } 
0
source

Source: https://habr.com/ru/post/886795/


All Articles