不能理解:使用lock()却从不执行代码
本文关键字:执行 代码 能理解 使用 lock 不能 | 更新日期: 2023-09-27 17:48:59
我正在学习c#中的线程,并且我得到了我无法理解的这种行为。
代码模拟I/O操作,如文件或串行端口,每次只有一个线程可以访问它,并且它会阻塞直到完成。
4个线程启动。每个只执行一次计数。它工作得很好,我可以看到表单上的计数在增长。但是有一个按钮可以从表单线程中计数。当我推它时,主线程就冻结了。调试器显示,其他线程继续计数,一个接一个,但表单线程永远无法访问资源。
1)为什么锁(tty)从表单线程永远无法访问它,当其他线程没有问题?2)是否有更好的方法来实现这种类型的同步?
抱歉,代码太大了:
public class MegaAPI
{
public int SomeStupidBlockingFunction(int c)
{
Thread.Sleep(800);
return ++c;
}
}
class UIThread
{
public delegate void dlComandoMaquina();
public class T0_SyncEvents
{
private EventWaitHandle _EventFechar; // Exit thread event
public T0_SyncEvents()
{
_EventFechar = new ManualResetEvent(false);
}
public EventWaitHandle EventFecharThread // Exit thread event
{
get { return _EventFechar; }
}
}
public class T0_Thread
{
private T0_SyncEvents _syncEvents;
private int _msTimeOut;
private dlComandoMaquina _ComandoMaquina;
public T0_Thread(T0_SyncEvents e, dlComandoMaquina ComandoMaquina, int msTimeOut)
{
_syncEvents = e;
_msTimeOut = msTimeOut;
_ComandoMaquina = ComandoMaquina;
}
public void VaiRodar() // thread running code
{
while (!_syncEvents.EventFecharThread.WaitOne(_msTimeOut, false))
{
_ComandoMaquina();
}
}
}
}
public partial class Form1 : Form
{
MegaAPI tty;
UIThread.T0_Thread thr1;
UIThread.T0_SyncEvents thrE1;
Thread Thread1;
int ACount1 = 0;
void UIUpdate1()
{
lock (tty)
{
ACount1 = tty.SomeStupidBlockingFunction(ACount1);
}
this.BeginInvoke((Action)delegate { txtAuto1.Text = ACount1.ToString(); });
}
UIThread.T0_Thread thr2;
UIThread.T0_SyncEvents thrE2;
Thread Thread2;
int ACount2 = 0;
void UIUpdate2()
{
lock (tty)
{
ACount2 = tty.SomeStupidBlockingFunction(ACount2);
}
this.BeginInvoke((Action)delegate { txtAuto2.Text = ACount2.ToString(); });
}
UIThread.T0_Thread thr3;
UIThread.T0_SyncEvents thrE3;
Thread Thread3;
int ACount3 = 0;
void UIUpdate3()
{
lock (tty)
{
ACount3 = tty.SomeStupidBlockingFunction(ACount3);
}
this.BeginInvoke((Action)delegate { txtAuto3.Text = ACount3.ToString(); });
}
UIThread.T0_Thread thr4;
UIThread.T0_SyncEvents thrE4;
Thread Thread4;
int ACount4 = 0;
void UIUpdate4()
{
lock (tty)
{
ACount4 = tty.SomeStupidBlockingFunction(ACount4);
}
this.BeginInvoke((Action)delegate { txtAuto4.Text = ACount4.ToString(); });
}
public Form1()
{
InitializeComponent();
tty = new MegaAPI();
thrE1 = new UIThread.T0_SyncEvents();
thr1 = new UIThread.T0_Thread(thrE1, UIUpdate1, 500);
Thread1 = new Thread(thr1.VaiRodar);
Thread1.Start();
thrE2 = new UIThread.T0_SyncEvents();
thr2 = new UIThread.T0_Thread(thrE2, UIUpdate2, 500);
Thread2 = new Thread(thr2.VaiRodar);
Thread2.Start();
thrE3 = new UIThread.T0_SyncEvents();
thr3 = new UIThread.T0_Thread(thrE3, UIUpdate3, 500);
Thread3 = new Thread(thr3.VaiRodar);
Thread3.Start();
thrE4 = new UIThread.T0_SyncEvents();
thr4 = new UIThread.T0_Thread(thrE4, UIUpdate4, 500);
Thread4 = new Thread(thr4.VaiRodar);
Thread4.Start();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
thrE1.EventFecharThread.Set();
thrE2.EventFecharThread.Set();
thrE3.EventFecharThread.Set();
thrE4.EventFecharThread.Set();
Thread1.Join();
Thread2.Join();
Thread3.Join();
Thread4.Join();
}
int Mcount = 0;
private void btManual_Click(object sender, EventArgs e)
{
Cursor.Current = Cursors.WaitCursor;
lock (tty) // locks here ! Never runs inside! But the other threads keep counting..
{
Mcount = tty.SomeStupidBlockingFunction(Mcount);
txtManual.Text = Mcount.ToString();
}
Cursor.Current = Cursors.Default;
}
}
我怀疑你在WinForms中击中了Windows消息循环和线程。我不知道那是什么,但这里有一些提示:
你可以在backgroundWorker中运行按钮的任务,以使工作远离UI线程。这样就解决了锁的问题。从工具箱中拖动一个BackgroundWorker,并将其放在设计器中的窗体上,然后连接事件,即:
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
然后切换btManual_Click中的代码,像这样调用后台工作器:
backgroundWorker1.RunWorkerAsync();
然后:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Mcount = tty.SomeStupidBlockingFunction(Mcount);
this.BeginInvoke((Action)delegate { txtManual.Text = Mcount.ToString(); });
}
我省略了锁(tty),因为我宁愿在函数内部只看到一条这样的语句,而不是在函数外部看到五条。而不是锁定tty,我将创建一个这样的私有变量:
public class MegaAPI
{
private object sync = new object();
public int SomeStupidBlockingFunction(int c)
{
lock (this.sync)
{
Thread.Sleep(800);
return ++c;
}
}
}
其他地方则简化,例如:
void UIUpdate1()
{
ACount1 = tty.SomeStupidBlockingFunction(ACount1);
this.BeginInvoke((Action)delegate { txtAuto1.Text = ACount1.ToString(); });
}
因为你不能在后台工作线程还在处理的时候运行它,这里有一个快速而肮脏的解决方案:在它工作的时候禁用按钮:
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
然后:
private void btManual_Click(object sender, EventArgs e)
{
this.btManual.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.btManual.Enabled = true;
}
所以我建议:
- 保持一个lock()语句在需要的函数内部同步
- 保持锁对象私有
- 在后台工作线程上运行工作
默认情况下互斥锁不提供公平性。它们只是保证你的过程作为一个整体会向前发展。实现的工作是根据调度器的特征等选择最佳的线程来获得互斥锁。确保获得互斥锁的线程完成程序需要完成的任何工作是编码器的工作。
如果"错误的线程"获得互斥锁对你来说是一个问题,你做错了。互斥锁适用于没有"错误线程"的情况。如果你需要公平或可预测的调度,你需要使用提供它的锁原语或使用线程优先级。
当持有互斥锁的线程没有cpu限制时,互斥锁倾向于以奇怪的方式运行。线程获取互斥锁,然后重新调度它们自己。这将导致退化的调度行为,就像您看到的行为一样。(当然,它们不会破坏它们的保证,但它们的行为将远远不像理论上完美的互斥锁,也提供了诸如公平性之类的东西。)