C# 无法理解:使用锁()但从不执行代码
我正在学习C#中的线程,我得到了这种我无法理解的行为 该代码模拟I/O操作,如文件或串行端口,其中一次只有一个线程可以访问它,并且阻塞直到完成 启动四个线程。每个都只执行一次计数。它工作正常,我可以在表格上看到计数在增长。但是有一个按钮可以从窗体线程中计数。当我推它时,主线程冻结。调试器显示其他线程一个接一个地计数,但表单线程永远无法访问资源 1) 当其他线程没有问题时,为什么表单线程的锁(tty)永远无法访问它? 2) 有没有更好的方法来进行这种类型的同步 抱歉,代码太大了:C# 无法理解:使用锁()但从不执行代码,c#,multithreading,locking,thread-safety,deadlock,C#,Multithreading,Locking,Thread Safety,Deadlock,我正在学习C#中的线程,我得到了这种我无法理解的行为 该代码模拟I/O操作,如文件或串行端口,其中一次只有一个线程可以访问它,并且阻塞直到完成 启动四个线程。每个都只执行一次计数。它工作正常,我可以在表格上看到计数在增长。但是有一个按钮可以从窗体线程中计数。当我推它时,主线程冻结。调试器显示其他线程一个接一个地计数,但表单线程永远无法访问资源 1) 当其他线程没有问题时,为什么表单线程的锁(tty)永远无法访问它? 2) 有没有更好的方法来进行这种类型的同步 抱歉,代码太大了: public c
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\u中切换您的代码,单击以如下方式调用后台工作程序:
backgroundWorker1.RunWorkerAsync();
public class MegaAPI
{
private object sync = new object();
public int SomeStupidBlockingFunction(int c)
{
lock (this.sync)
{
Thread.Sleep(800);
return ++c;
}
}
}
然后:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Mcount = tty.SomeStupidBlockingFunction(Mcount);
this.BeginInvoke((Action)delegate { txtManual.Text = Mcount.ToString(); });
}
private void btManual_Click(object sender, EventArgs e)
{
this.btManual.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
我省略了lock(tty),因为我希望在函数内部只看到其中一条语句,而不是在函数外部看到其中五条。我不会锁定tty,而是创建一个私有变量,如下所示:
backgroundWorker1.RunWorkerAsync();
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 backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Mcount = tty.SomeStupidBlockingFunction(Mcount);
this.BeginInvoke((Action)delegate { txtManual.Text = Mcount.ToString(); });
}
private void btManual_Click(object sender, EventArgs e)
{
this.btManual.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
以及:
因此,我建议:
- 保留一个lock()语句 在需要 同步
- 保持锁定对象的私有性
- 在后台工作程序上运行该工作
- 默认情况下,互斥锁不提供公平性。他们只是保证你的整个过程会取得进展。实现的工作是根据调度器的特性等选择最佳线程来获取互斥锁。编码器的工作是确保获取互斥锁的线程执行程序需要完成的任何工作
如果“错误线程”获得互斥锁对您来说是个问题,那么您就是做错了。互斥体用于没有“错误线程”的情况。如果需要公平性或可预测的调度,则需要使用提供它的锁定原语或使用线程优先级
当包含互斥对象的线程不受CPU限制时,互斥对象往往会以奇怪的方式运行。线程获取互斥锁,然后重新安排自己的时间。这将导致退化的调度行为,就像您看到的行为一样。(当然,它们不会破坏它们的保证,但它们的行为不像理论上完美的互斥体那样,也提供了公平性。)使用mutex.WaitOne()和mutex.ReleaseMutex()而不是lock(),可以正常工作。这是一个好问题。在我的机器上(有2个内核),如果只有2个非UI线程就可以了,但是当第3个线程未注释时,就会按照所述锁定。我知道有一种可能性,一个正在等待的线程可能会被接管去做一些其他的工作,然后被放回等待队列的末尾,而不是开始——但我无法确定这里是否发生了这种情况。