简体   繁体   English

无法理解:使用lock()但从不执行代码

[英]Cant understand: using lock () but never executing code

I'm learning about threads in C#, and i get this behavior that i cant understand. 我正在学习C#中的线程,并且得到了我无法理解的行为。

The code simulates I/O operations, like files or serial port, where only one thread can access it at time, and it blocks until finishes. 该代码模拟文件或串行端口等I / O操作,其中一次只能有一个线程可以访问它,并且阻塞直到完成。

Four threads are started. 启动了四个线程。 Each performs just a count. 每个仅执行一个计数。 It works ok, i can see on the form the counts growing. 一切正常,我可以在表格上看到数量的增长。 But there is a button to count from the form thread. 但是有一个按钮可以从表单线程进行计数。 When i push it, the main thread freezes. 当我按下它时,主线程冻结。 The debugger shows that the others threads keep counting, one by one, but the form thread never gets access to the resource. 调试器显示,其他线程一直在一个接一个地计数,但是表单线程从不访问资源。

1) Why the lock(tty) from the form thread never gets access to it, when the others threads has no problem ? 1)为什么其他线程没有问题时,来自表单线程的lock(tty)永远无法访问它? 2) Is there a better way to do this type of synchronization ? 2)有没有更好的方法来进行这种类型的同步?

Sorry about the big code: 对不起,大代码:

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;
    }
}

I suspect you are hitting something with the Windows message loop and threading in WinForms. 我怀疑您在Windows消息循环和WinForms中的线程中遇到了问题。 I don't know what that is, but here are a few pointers: 我不知道那是什么,但是这里有一些提示:

You can run the button's task in a backgroundWorker to keep the work off the UI thread. 您可以在backgroundWorker中运行按钮的任务,以使工作远离UI线程。 That solves the lock problem. 这样就解决了锁定问题。 Drag a BackgroundWorker from the toolbox and drop it on your Form in the designer, and hook up the event, ie: 从工具箱中拖动BackgroundWorker并将其放在设计器中的Form上,并挂接事件,即:

this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);

then switch your code in btManual_Click to call the background worker like this: 然后在btManual_Click中切换代码以像这样调用后台工作程序:

backgroundWorker1.RunWorkerAsync();

and then: 接着:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    Mcount = tty.SomeStupidBlockingFunction(Mcount);
    this.BeginInvoke((Action)delegate { txtManual.Text = Mcount.ToString(); });
}

I've left out the lock (tty) because I would rather see only one of these statements inside the function, rather than five of them outside. 我省去了锁(tty),因为我希望只在函数内部看到这些语句之一,而不是在外部看到其中的五个语句。 And instead of locking on tty, I would create a private variable like this: 除了锁定tty之外,我还可以创建一个私有变量,如下所示:

public class MegaAPI
{
    private object sync = new object();

    public int SomeStupidBlockingFunction(int c)
    {
        lock (this.sync)
        {
            Thread.Sleep(800);
            return ++c;                
        }
    }
}

Everywhere else is then simplified, for example: 然后简化其他所有地方,例如:

void UIUpdate1()
{
    ACount1 = tty.SomeStupidBlockingFunction(ACount1);
    this.BeginInvoke((Action)delegate { txtAuto1.Text = ACount1.ToString(); });
}

And since you can't run the background worker while it's still processing, here is a quick-and-dirty solution: disable the button while it's working: 而且由于您无法在后台处理程序仍在运行时运行它,所以这里有一个快速而又肮脏的解决方案:在工作时禁用该按钮:

this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);

and then: 接着:

private void btManual_Click(object sender, EventArgs e)
{
    this.btManual.Enabled = false;
    backgroundWorker1.RunWorkerAsync();
}

and: 和:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    this.btManual.Enabled = true;
}

So I recommend: 所以我建议:

  • Keep a single lock () statement inside the function needing the synchronization 在需要同步的函数内保留一个lock()语句
  • Keep the lock object private 保持锁定对象不公开
  • Run the work on a background worker 在后台工作人员上运行工作

Mutexes do not provide fairness by default. 互斥体默认情况下不提供公平性。 They just guarantee that your process as a whole will make forward progress. 他们只是保证您的整个流程将取得进步。 It is the implementation's job to pick the best thread to get the mutex based on characteristics of the scheduler and so on. 实现的工作是根据调度程序的特性等选择最佳线程来获取互斥体。 It is the coder's job to make sure that the thread that gets the mutex does whatever work the program needs done. 确保获取互斥量的线程完成程序需要完成的工作是编码人员的工作。

If it's a problem for you if the "wrong thread" gets the mutex, you are doing it wrong. 如果“错误的线程”获取了互斥锁对您来说是个问题,那么您就错了。 Mutexes are for cases where there is no "wrong thread". 互斥对象用于没有“错误线程”的情况。 If you need fairness or predictable scheduling, you need to use a locking primitive that provides it or use thread priorities. 如果需要公平性或可预测的调度,则需要使用提供公平性或线程优先级的锁定原语。

Mutexes tend to act in strange ways when threads that hold them aren't CPU-limited. 当持有互斥对象的线程不受CPU限制时,互斥对象往往以奇怪的方式工作。 Your threads acquire the mutex and then deschedule themselves. 您的线程获取互斥锁,然后自行调度。 This will lead to degenerate scheduling behavior just like the behavior you're seeing. 就像您看到的行为一样,这将导致退化的调度行为。 (They won't break their guarantees, of course, but they will act much less like a theoretically perfect mutex that also provided things like fairness.) (当然,他们不会破坏自己的保证,但是它们的行为将不像理论上完美的互斥体,而互斥体也提供了公平性。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM