Multithreading mono挂起而MS.Net不挂起的线程问题';T
我正在用mono测试我的应用程序,以防Linux端口出现问题。我最初考虑在这里粘贴3000行代码,但最后我设计了一个小示例;) 您有一个带有按钮的表单(诗意地命名为Multithreading mono挂起而MS.Net不挂起的线程问题';T,multithreading,mono,invoke,begininvoke,Multithreading,Mono,Invoke,Begininvoke,我正在用mono测试我的应用程序,以防Linux端口出现问题。我最初考虑在这里粘贴3000行代码,但最后我设计了一个小示例;) 您有一个带有按钮的表单(诗意地命名为Button1),还有一个标签(毫无疑问,标签上有名称Label1)。所有人都在一种叫做Form1的形式上过着幸福的生活。单击按钮1启动一个无限循环,该循环递增本地计数器,并更新标签1(使用调用)以反映其值 现在在Mono中,如果调整表单大小,标签将停止更新,永远不会重新启动。MS实现不会出现这种情况BeginInvoke没有更好的效
Button1
),还有一个标签(毫无疑问,标签上有名称Label1
)。所有人都在一种叫做Form1
的形式上过着幸福的生活。单击按钮1
启动一个无限循环,该循环递增本地计数器,并更新标签1
(使用调用
)以反映其值
现在在Mono中,如果调整表单大小,标签将停止更新,永远不会重新启动。MS实现不会出现这种情况BeginInvoke
没有更好的效果;更糟糕的是,这两种情况下都会使UI挂起
你知道这种差异是从哪里来的吗?你将如何解决它?最后,为什么BeginInvoke不在这里工作?我一定是犯了个大错误。。。但是哪个呢
编辑: 迄今取得的一些进展:
- 调用BeginInvoke实际上起作用;只是,UI刷新速度不够快,所以它似乎停止了
- 在mono上,当您在UI队列中插入消息时(例如通过调整表单大小),整个线程将挂起。事实上,同步
调用永远不会返回。我想知道为什么Invoke
- 有趣的是:即使使用
,异步调用也不会在调整大小操作结束之前执行。在MS.Net上,它们在调整大小的同时保持运行BeginInvoke
代码如下所示(C版本更低): 或者,在C#中
首先也是最重要的:点击Button1已经是异步的,所以你不需要创建另一个线程来递增,只需调用递增方法。对不起,我正在逐行阅读你的问题,当我进入while循环时,我忘记了按钮:
private void Button1_Click(System.Object sender, System.EventArgs e)
{
Thread t = new Thread(Increment);
t.IsBackground = true;
t.Start();
}
第二:如果您确实需要使用线程,那么您应该始终将线程设置为背景(即),除非您有充分的理由使用前景线程
Third:如果要更新UI,则应检查invokererequired
属性并调用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
}
}
您还可以“自动”调用所需的“模式”:
现在看看你是否还有同样的问题
我在我的机器上试过,效果很好:
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);
}
}
}
你知道这种差异在哪里吗
来自哪里?你将如何解决它
我不确定。我在你的代码中没有看到任何明显的东西会导致Mono和.NET之间的差异。如果非要我胡乱猜测的话,我会说你有可能在Mono中偶然发现了一个模糊的bug。不过,我认为Mono可能会使用一种完全不同的机制来处理WM_PAINT消息,从而导致表单刷新。重复调用Invoke
可能会破坏Mono刷新表单的能力,从而不断地重击UI线程
最后,为什么不开始思考
在这里工作
在紧循环中调用Invoke
已经够糟糕的了,但是BeginInvoke
会更糟。工作线程正在淹没UI消息泵BeginInvoke
不会等待UI线程完成委托的执行。它只是发布请求并快速返回。这就是为什么它看起来很悬。BeginInvoke
发布到UI消息队列的消息不断增加,因为工作线程可能严重超出UI线程处理这些消息的能力
其他评论
我还应该提到,工作线程在代码中几乎是无用的。原因是您在每次迭代中都要调用Invoke
<代码>调用阻塞,直到UI完成委托的执行。这意味着您的工作线程和UI线程基本上是同步的。换句话说,工作者大部分时间都在等待UI,反之亦然
解决方案
一种可能的修复方法是降低调用Invoke
的速度。不要在每次循环迭代时调用它,而是尝试每1000次迭代或类似的操作一次
更好的方法是根本不使用Invoke
或BeginInvoke
。就我个人而言,我认为这些更新UI的机制被过度使用了。让UI线程控制自己的更新速度几乎总是更好的,特别是当工作线程正在进行连续处理时。这意味着您需要在表单上放置一个计时器,并使其以所需的刷新率进行计时。从勾选
事件中,您将探测工作线程正在更新的共享数据结构,并使用该信息更新表单上的控件。这有几个优点
- 它打破了UI和
强加的工作线程之间的紧密耦合Control.Invoke
- 它将更新UI线程的责任放在它应该属于的UI线程上
- UI线程可以指定更新的时间和频率
- UI消息泵不会像工作线程启动的封送处理技术那样溢出
- 工作线程不必等待更新已执行的确认,然后再继续执行下一步(即,在UI和工作线程上都可以获得更高的吞吐量)
- 这是mono运行时的一个bug,至少我认为是这样。这段代码可能不是一个好的实践(我不是线程专家),但表明一个bug的事实是windows和Linux上的行为不同
在Linux上,mono具有完全相同的行为
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
}
}
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);
}
}
}