开发者

Cant understand: using lock () but never executing code

开发者 https://www.devze.com 2023-02-22 03:23 出处:网络
I\'m learning about threads in C#, and i get this behavior that i cant understand. The code simulates I/O operations, like files or serial port, where only one thread can access it at time, and it bl

I'm learning about threads in C#, and i get this behavior that i cant understand.

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.

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 ? 2) Is there a better way to do this type of synchronization ?

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, FormClosingEv开发者_高级运维entArgs 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. 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. 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, i.e.:

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

then switch your code in btManual_Click to call the background worker like this:

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. And instead of locking on tty, I would create a private variable like this:

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
  • 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. 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.)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号