Using lock to synchronize agent event with main UI thread...

C

christer.dk

Hi all,

We're doing an application in which a background agent is polling
asynchronously for data and doing some work. When having new data it
throws an event. A method is wired to the event, uses Invoke for
updating selected datagrids etc. However, sometimes, the user is doing
work (like executing a save method at the same time), and we want to
postpone the redraw of the datagrids, or delay users input a little
until updating is completed. All in all, we want to "syncronize" the
events regarding updating and workm and what is shown.

I may be misunderstanding the use of the lock statement, but basicaly
the logic behind the code below is that if a lock statement is entered
using the thelock object, other lock statments using the same object
are put in "queue" and waits until the previous lock statement is
exited.

The code below is a form with two buttons. The first button starts a
thread, simulating the agent throwing the event. The eventhandler uses
Invoke to invoke Cross. The Cross method does some work (not a all near
15 seconds in real life, this is for show only). While entering and
working (before the 15 seconds have past), the second button is
pressed. I would expect the second buttons event handler to wait at the
lock statement until the Cross method releases it, but I get this
result:

15:31:58: Cross entering lock
15:31:58: Cross entered lock
15:31:59: Button2 entering lock
15:31:59: Button2 entered lock
15:32:00: Button2 exited lock
15:32:14: Cross exited lock

My expected result would be:

Cross entering lock
Cross entered lock
Cross exited lock
Button2 entering lock
Button2 entered lock
Button2 exited lock

or vice versa, regarding whoever comes first... but not mixed...

After some testing I'm wondering if this is only valid as an idea. Is
this the right way to do what I want? Am I misunderstanding the lock
(is the lock only usable on the same code, not scattered locks in
several methods? I have however seen property accessors that uses the
same lock object, so...)

PS. The while statement with sleep and DoEvents might also be wrong for
simulating work while still being able to take input from the user.
Maybe the example is done wrong, and the methods actually would be
synchronized in a real situation?

You're welcome to answer me in hard technical terms, I'm just fairly
new to threaded windows programming...

Thanks in advance!

Christer



public partial class Form1 : Form
{
private object thelock;
public Form1()
{
thelock = new object();
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
//Start worker thread
ThreadStart ts = new ThreadStart(ThreadWork);
Thread thread = new Thread(ts);
thread.Start();
}

private void button2_Click(object sender, EventArgs e)
{
Debug.Write(DateTime.Now.ToLongTimeString() + ": Button2
entering lock" + Environment.NewLine);
lock (thelock)
{
Debug.Write(DateTime.Now.ToLongTimeString() + ":
Button2 entered lock" + Environment.NewLine);
MessageBox.Show("Button executed");
}
Debug.Write(DateTime.Now.ToLongTimeString() + ": Button2
exited lock" + Environment.NewLine);

}

private void ThreadWork()
{
this.Invoke(new MethodInvoker(Cross));
}

private void Cross()
{
Debug.Write(DateTime.Now.ToLongTimeString() + ": Cross
entering lock" + Environment.NewLine);
lock (thelock)
{
Debug.Write(DateTime.Now.ToLongTimeString() + ": Cross
entered lock" + Environment.NewLine);

DateTime seconds15 = DateTime.Now.AddSeconds(15);
//Wait 15 seconds...
while (DateTime.Now < seconds15)
{
Thread.Sleep(15);
Application.DoEvents();
}

MessageBox.Show("Cross executed");
}
Debug.Write(DateTime.Now.ToLongTimeString() + ": Cross
exited lock" + Environment.NewLine);
}
}
 
C

christer.dk

Hi Jakob,

Thank you for your quick reply. What you suggest is actually what I
tried to get away from. I experienced a deadlock situation by having
lock statement in ThreadWork, wrapping the Invoke statement. This is
basically what happened in our real situation, translated to the code
below:

ThreadWork enters lock in its own thread.
button2_Click tries to enter lock, and waits for ThreadWork to finish.
ThreadButton now uses Invoke to execute Cross (this fails because main
UI thread (Button2_Click) is waiting for ThreadWork to finish - my
personal interpretation, that is ;-)

The basic problem here, as I see it, is that this is not two
independent threads just needing to synchonize, but one is dependant on
the other for UI updates....

Is my problem clear enough?

Kind regards,
Christer



Jakob Christensen skrev:
 
C

christer.dk

Hi Jakob,

I looked through the documentation on the suggested subject and the
example code. This might be the way to go. However, in my UI class
(running on UI thread) there are several locked methods using the same
lock object. Maybe, as we get further into this subjct, this strategy
would be wrong.
The reason behind these multiple locks is that the time of the event
raised by the agent is "unknown", and several user actions should be
safe from a forced update from ThreadWork. Therefore these crucial
points in code are locked (dont worry, i haven't used lock all over the
place).

In the example code, the Result method, I interpret as "master" code
(initiator), and the calculation methods as "slave" code that can run
independently (workers) . But I'm not sure on how this should be maps
to my code, as theres actually several pieces of "master" code, that
the "slave" event should wait for?

Before I saw your answer, I was actually thinking in the directions of
a minimalistic producer/consumer implementation, where the only thing
the two threads share is a variable (get/set access secured by lock)
and thereby eliminating the need for the Invoke cross over.

I would appreciate if you could elaborate more on how ManualResetEvent
could help me in this situation.

Cheers!
Christer




Jakob Christensen skrev:
I see your problem :) Sorry I missed out on that.

The worker thead should not be allowed to lock. You can accomplish this by
using ManualResetEvent. If you reset your ManualResetEvent object at the top
of button2_Click and then signal it at the bottom, you can let ThreadWork
wait for the signal before it may continue. Check out ManualResetEvent in
MSDN.

This will keep the datagrid from updating when the user is doing something
else.

BTW, using sleep on your main thread is not a good idea.

HTH, Jakob.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top