我正在使用单声道测试我的应用程序预装Linux端口,我有一个线程问题。我最初考虑在这里粘贴3000个代码行,但最后我设计了一个小的最小例子;)
你有一个带有按钮的表单(诗意地命名为Button1
和一个标签(毫无意外地,名称为Label1
))。整个地段都在一个名为Form1
的形式上过着幸福的生活。点击Button1
会启动一个无限循环,增加本地计数器并更新Label1
(使用Invoke
)以反映其值。
现在在Mono中,如果您调整表单大小,标签将停止更新,永远不会重新启动。 MS实现不会发生这种情况。 BeginInvoke
效果不佳;更糟糕的是,它使UI在两种情况下都挂起。
你知道这种差异来自哪里吗?你会如何解决它?最后,为什么BeginInvoke不在这里工作?我一定是犯了一个大错...但是哪个?
<小时/> 的修改: 到目前为止取得了一些进展:
Invoke
调用永远不会返回。我想了解原因。BeginInvoke
,在调整大小操作结束之前也不会执行异步调用。在MS.Net上,它们在调整大小时继续运行。代码看起来像这样(C#版本更低):
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
或者,在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) {
}
}
}
答案 0 :(得分:5)
这是单声道运行时的错误,至少我认为是这样。代码可能不是一个好的做法(我不是一个线程专家),但暗示一个错误的事实是Windows和Linux上的行为不同。
在Linux上,mono与MS.Net在Windows上的行为完全相同。即使在调整大小时也不会挂起,不断更新。
在Windows上,mono显示上述所有问题。我在https://bugzilla.novell.com/show_bug.cgi?id=690400发布了一个错误报告。
答案 1 :(得分:1)
你知道这种差异在哪里吗? 来自?你会如何解决它?
我不确定。我没有在代码中看到任何明显会导致Mono和.NET之间差异的内容。如果我不得不做出一个疯狂的猜测,我会说你有可能偶然发现Mono中一个不起眼的错误。虽然,我认为Mono可能使用一种完全不同的机制来处理导致表单刷新的WM_PAINT消息。从重复调用Invoke
开始持续冲击UI线程可能会破坏Mono刷新表单的能力。
最后,为什么不是BeginInvoke 在这里工作?
在紧密循环中调用Invoke
已经够糟糕了,但BeginInvoke
会更糟。工作线程充斥着UI消息泵。 BeginInvoke
不等到UI线程完成委托的执行。它只是发布请求并快速返回。这就是它似乎挂起的原因。 BeginInvoke
发布到UI消息队列的消息不断增加,因为工作线程可能严重超出了UI线程处理它们的能力。
其他评论
我还应该提一下,工作线程在代码中几乎没用。原因是因为您在每次迭代时都会调用Invoke
。 Invoke
阻塞,直到UI完成委托的执行。这意味着您的工作线程和UI线程基本上彼此锁定。换句话说,工作人员大部分时间都在等待UI,反之亦然。
<强>解决方案强>
一种可能的解决方法是降低调用Invoke
的速率。而不是在每次循环迭代时调用它,而是每1000次迭代等尝试一次。
任何更好的方法都是不要使用Invoke
或BeginInvoke
。就个人而言,我认为这些用于更新UI的机制被过度使用。让UI线程限制自己的更新速率几乎总是更好,特别是当工作线程进行连续处理时。这意味着您需要在表单上放置一个计时器,并以所需的刷新率打勾。从Tick
事件中,您将探测工作线程正在更新的共享数据结构,并使用该信息更新表单上的控件。这有几个好处。
Control.Invoke
强加的UI和工作线程之间的紧密耦合。答案 2 :(得分:0)
首先: 点击Button1已经异步,所以你不需要创建另一个线程来增加,只需调用increment方法抱歉,我是逐行阅读你的问题,当我到达while循环时,我忘记了按钮:
private void Button1_Click(System.Object sender, System.EventArgs e)
{
Thread t = new Thread(Increment);
t.IsBackground = true;
t.Start();
}
第二:如果您确实需要使用线程,那么除非您有充分的理由使用前台线程,否则应始终将线程设置为后台(即foreground prevents your process from terminating)。
第三:如果您要对用户界面进行更新,那么您应该检查InvokeRequired
属性并致电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
}
}
您还可以“自动化”Invoke Required“模式”:Automating the InvokeRequired code pattern
现在看看你是否还有同样的问题。
我在我的机器上尝试过,它就像一个魅力:
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);
}
}
}