multiple threads writing to WebBrowser, getting deadlocked

Z

Zytan

I have multiple threads writing to WebBrowser (using a function that
checks InvokedRequired, and if so, invokes itself on the WebBrowser
thread) and they are getting deadlocked.

They only deadlock when I use lock { } around the call to
WebBrowser.Write to ensure thread safety!

Does any one have experience with such a thing?

Zytan
 
J

Jon Skeet [C# MVP]

Zytan said:
I have multiple threads writing to WebBrowser (using a function that
checks InvokedRequired, and if so, invokes itself on the WebBrowser
thread) and they are getting deadlocked.

They only deadlock when I use lock { } around the call to
WebBrowser.Write to ensure thread safety!

Does any one have experience with such a thing?

Hang on - do you mean you've already got a lock when you call Invoke?

Could you post a short but complete program which demonstrates the
problem?

See http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.
 
B

Barry Kelly

Zytan said:
I have multiple threads writing to WebBrowser (using a function that
checks InvokedRequired, and if so, invokes itself on the WebBrowser
thread) and they are getting deadlocked.

They only deadlock when I use lock { } around the call to
WebBrowser.Write to ensure thread safety!

Using 'lock' doesn't make sense.

WebBrowser is a WinForms control, and should only be accessed from its
UI thread. So, if you use Control.InvokeRequired, and properly use
Control.Invoke or Control.BeginInvoke to marshal your call over to the
UI control, you don't need to use 'lock', because all code that touches
the WebBrowser control is serialized on its UI thread.

Access to Control.InvokeRequired is not required to be synchronized - in
fact, it would be easy to deadlock if it and its ilk did require
synchronization. Similarly, Control.Invoke, Control.BeginInvoke etc.
don't require synchronization - check docs for Control.InvokeRequired.

-- Barry
 
A

Alun Harford

Zytan said:
I have multiple threads writing to WebBrowser (using a function that
checks InvokedRequired, and if so, invokes itself on the WebBrowser
thread) and they are getting deadlocked.

They only deadlock when I use lock { } around the call to
WebBrowser.Write to ensure thread safety!

Does any one have experience with such a thing?

Well if you do:

class Foo
{
//Some stuff to setup a WebBrowser control called WebBrowser

public void SomeMethod()
{
lock(WebBrowser)
{
if(InvokeRequired)
{
Invoke(new SomeDelegate(SomeMethod));
} else {
//Do something
}
}
}
}

This will result in a deadlock when called from a thread other than the
UI thread. Instead, you should do:

class Foo
{
//Some stuff to setup a WebBrowser control called WebBrowser

public void SomeMethod()
{
if(InvokeRequired)
{
Invoke(new SomeDelegate(SomeMethod));
} else {
lock(WebBrowser)
{
//Do something
}
}
}
}
}

If you want more than that, post some code :)

Alun Harford
 
Z

Zytan

Hang on - do you mean you've already got a lock when you call Invoke?

Yes.
Could you post a short but complete program which demonstrates the
problem?

Ok, I made a small program that does nothing but start a timer that
ticks every second. It calls MyWriteLine. This calls (using
InvokeRequired + Invoke, if needed) WebBrowser.Document.Write. (Well,
it makes sure the WebBrowser has a Document to write to, and if not,
navgiates to "about:blank" first).

Then, I made a thread that I can start/stop with two buttons, this
thread does the same thing as the timer. The stop button calls
myThread.Join(); to wait for the thread to complete.

Starting the thread is fine, they both update te WebBrowser. Stopping
the thread hangs on myThread.Join();

I thought this is a problem with synchronization, so I made a wrapper
to MyWriteLine, and placed a critical section lock into. Thus,
MyWriteLine is only called within a critical section. Now, the
program hangs at the lock statement when I stop the thread.

I'd post the code, but I don't have it with me at the moment. I'll
code it again, and do some more testing. I have a feeling that
replacing WebBrowser with another control solves the problem.

Zytan
 
Z

Zytan

WebBrowser is a WinForms control, and should only be accessed from its
UI thread. So, if you use Control.InvokeRequired, and properly use
Control.Invoke or Control.BeginInvoke to marshal your call over to the
UI control, you don't need to use 'lock', because all code that touches
the WebBrowser control is serialized on its UI thread.

Ok, I knew that Control.Invoke made the call on the same thread as
Control, but, I am unaware of what it's doing internally. I don't
know what 'marshal' actually means. So, it basically means it is
serialized on the Control's thread, meaning that all calls to
Control.Invoke are called in order, one after the other, and thus
multiple threads all calling Control.Invoke to update a single control
will be safe.
Access to Control.InvokeRequired is not required to be synchronized - in
fact, it would be easy to deadlock if it and its ilk did require
synchronization. Similarly, Control.Invoke, Control.BeginInvoke etc.
don't require synchronization - check docs for Control.InvokeRequired.

Ok, thanks, Barry

Zytan
 
Z

Zytan

Well if you do:
class Foo
{
//Some stuff to setup a WebBrowser control called WebBrowser

public void SomeMethod()
{
lock(WebBrowser)
{
if(InvokeRequired)
{
Invoke(new SomeDelegate(SomeMethod));
} else {
//Do something
}
}
}
}

Ha ha, yes, I wasn't stupid enough to recusively attempt to enter the
same lock. The lock I have is in a wrapper around SomeMethod().
If you want more than that, post some code :)

I will once I make my small source example again.

Zytan
 
Z

Zytan

Code example: Make two Buttons (btnStart, btnStop), and one
WebBroswer (webLog), and paste this into the main Form:

Thread myThread;
private volatile bool m_ThreadProcessing = false;
private int m_count = 0;

private void ThreadFunc()
{
while (m_ThreadProcessing)
{
Thread.Sleep(400);
m_count++;
MyWrite(webLog, m_count + "<br>" +
Environment.NewLine);
}
}

private void btnStart_Click(object sender, EventArgs e)
{
if (myThread == null)
{
myThread = new Thread(ThreadFunc);
m_ThreadProcessing = true;
myThread.Start();
}
}

private void btnStop_Click(object sender, EventArgs e)
{
if (myThread != null)
{
m_ThreadProcessing = false;
myThread.Join(); // <------------- HANGS HERE!
myThread = null;
}
}

delegate void MyWrite_Delegate(WebBrowser web, string str);
public static void MyWrite(WebBrowser web, string str)
{
if (web.InvokeRequired)
{
MyWrite_Delegate funcptr = MyWrite;
object[] args = { web, str };
web.Invoke(funcptr, args);
}
else
{
if (web.Document == null) web.Navigate("about:blank");
web.Document.Write(str);
web.Document.Window.ScrollTo(0, int.MaxValue); //
scroll to bottom
}
}

Note that this hangs only with this single thread accessing the
WebBrowser. Not even the main thread accesses it! (I'm preparing for
you guys to show me the obvious error I've been missing all day.)

Zytan
 
J

Jon Skeet [C# MVP]

Code example: Make two Buttons (btnStart, btnStop), and one
WebBroswer (webLog), and paste this into the main Form:

<snip>

Yes, that would hang. You're using "Invoke" from myThread - that will
block until the delegate you've passed it has been executed on the UI
thread.

Now, from the UI thread, you're calling myThread.Join - that will
block until myThread has completed.

So, how can either of them get anywhere?

Jon
 
Z

Zytan

Yes, that would hang. You're using "Invoke" from myThread - that will
block until the delegate you've passed it has been executed on the UI
thread.

Now, from the UI thread, you're calling myThread.Join - that will
block until myThread has completed.

Ok, it's a deadlock.

I was uncertain as what happened as a result of Invoke. Thanks to
your reply, I have a better idea. The message queue in the main
thread is processing the 'stop button clicked' message, which is
running Thread.Join, which is waiting for myThread to terminate. The
message queue is stalled (never a good idea).

myThread is upset that it doesn't own the control (as it shouldn't,
since it doesn't have a message queue to maintain it), so it tells the
GUI thread to deal with it, via Invoke. Now, my understanding is that
Invoke is *not a function pointer call* (perhaps this is why they call
them delegates, instead of function pointers?), it is a message placed
in the GUI thread. (This is why an earlier post mentioned that
Control.Invoke is serialized, meaning they are handled one at a time,
because the message queue processes one message at a time. So, if
multiple threads posted multiple messages into the queue, it still
does one at a time, and my WebBrowser doesn't need a critical
section.) But, the GUI thread has its message queue stalled.

Solution #1: Use BeginInvoke, which is a 'non blocking' call (it must
call Win32's PostMessage instead of SendMessage).

Solution #2: Don't use Thread.Join(). The examples show it, so I
used it. Often, it is unneeded.

Solution #3: Don't make the worker threads access the GUI.

Solution #4: Use BackgroundWorker, which I presume doesn't have this
issue (I think it calls a function of yours when it is done, so you
never have to call something like BackgroundWorker.Join, which likely
doesn't exist).

Does that all make sense?

Zytan
 
Z

Zytan

private void ThreadFunc()
{
while (m_ThreadProcessing)
{
Thread.Sleep(400);
m_count++;
MyWrite(webLog, m_count + "<br>" +
Environment.NewLine);
}
}

For the curious:

Move the Thread.Sleep(400) to AFTER the MyWrite call, and it works,
always. Thanks to Jon's great reply, I now know why. This single
difference was the difference between two identical apps I made, and I
was dumbfounded as to why one worked and the other didn't.
Thread.Sleep() at the end doesn't give the deadlock of Control.Invoke
and Thread.Join to occur.

Thanks, Jon.

Zytan
 
J

Jon Skeet [C# MVP]

For the curious:

Move the Thread.Sleep(400) to AFTER the MyWrite call, and it works,
always. Thanks to Jon's great reply, I now know why. This single
difference was the difference between two identical apps I made, and I
was dumbfounded as to why one worked and the other didn't.
Thread.Sleep() at the end doesn't give the deadlock of Control.Invoke
and Thread.Join to occur.

Hmm. You've still got a potential deadlock, unfortunately.

If you click the stop button at just the wrong time, it will still
break. You can end up with btnStop_Click running, waiting for myThread
to finish - but with myThread waiting for a call to web.Invoke to
finish.

Using BeginInvoke is certainly a reasonable way of avoiding this,
although I'd avoid calling Thread.Join in the UI thread too.
 
Z

Zytan

Hmm. You've still got a potential deadlock, unfortunately.

Yes, I knew that... I should have been accurate and said that if you
click it just at the right time, you have a chance to create the
deadlock, since the same conditions still exist. But, the likelihood
is 1 in a billion.

I realy didn't mean this was a solution. I meant to show how code
that you would think is broken appears to run fine. It's still
broken. And this really threw me off.
Using BeginInvoke is certainly a reasonable way of avoiding this,
although I'd avoid calling Thread.Join in the UI thread too.

Yes, I choose both (call Control.BeginInvoke, and to not call
Thread.Join)

Zytan
 
V

verbiest

Well if you do:

class Foo
{
//Some stuff to setup a WebBrowser control called WebBrowser

public void SomeMethod()
{
lock(WebBrowser)
{
if(InvokeRequired)
{
Invoke(new SomeDelegate(SomeMethod));
} else {
//Do something
}
}
}

}

This will result in a deadlock when called from a thread other than the
UI thread. Instead, you should do:

class Foo
{
//Some stuff to setup a WebBrowser control called WebBrowser

public void SomeMethod()
{
if(InvokeRequired)
{
Invoke(new SomeDelegate(SomeMethod));
} else {
lock(WebBrowser)
{
//Do something
}
}
}
}

}

If you want more than that, post some code :)

Alun Harford

I you don't need returnvalues or out-parameters, it is better to use
BeginInvoke() instead of Invoke().

See here: http://kristofverbiest.blogspot.com/2007/02/avoid-invoke-prefer-begininvoke.html

Kristof
 
R

Ravichandran J.V.

When you have the primary thread, owning the window, sleeping it usually
blocks the messages for the time that you put it to sleep. This is
because Windows identifies threads in two ways: as a UI thread or a
worker thread and the UI thread is meant to handle all GUI operations.
Obviously, if the UI thread (and if it is a primary thread as well) is
put to sleep, it will seem to simulate a deadlock whereas, it may just
be a matter of blocking messages.

Please refer to the following blog for details on sleeping on a UI
thread:

http://blogs.msdn.com/oldnewthing/archive/2006/02/10/529525.aspx

Another can be found in the below link to msdn:

http://msdn.microsoft.com/msdnmag/issues/04/05/BasicInstincts/

and on:

http://www.yoda.arachsys.com/csharp/threads/winforms.shtml


with regards,


J.V.Ravichandran
- http://www.geocities.com/
jvravichandran
- Or, just search on "J.V.Ravichandran"
at http://www.Google.com
 
J

Jon Skeet [C# MVP]

When you have the primary thread, owning the window, sleeping it usually
blocks the messages for the time that you put it to sleep. This is
because Windows identifies threads in two ways: as a UI thread or a
worker thread and the UI thread is meant to handle all GUI operations.
Obviously, if the UI thread (and if it is a primary thread as well) is
put to sleep, it will seem to simulate a deadlock whereas, it may just
be a matter of blocking messages.

It was the worker thread which had the call to Sleep in it. The UI
thread had the call to Join in it, and that *was* deadlocking (rather
than just waiting for messages which would get processed eventually)
because the thread it was trying to join was also waiting for a
message to be completed in the UI thread.

Jon
 
R

Ravichandran J.V.

<snip>
Code example: Make two Buttons (btnStart, btnStop), and one
WebBroswer (webLog), and paste this into the main Form:
</snip>

Does this not mean that the main form would contain the main method and
if I remember the signature well, it would be a STA thread and hence,
would be the main thread as well. Would this not make the thread the UI
thread or am I missing something?

with regards,


J.V.Ravichandran
- http://www.geocities.com/
jvravichandran
- Or, just search on "J.V.Ravichandran"
at http://www.Google.com
 
J

Jon Skeet [C# MVP]

<snip>
Code example: Make two Buttons (btnStart, btnStop), and one
WebBroswer (webLog), and paste this into the main Form:
</snip>

Does this not mean that the main form would contain the main method and
if I remember the signature well, it would be a STA thread and hence,
would be the main thread as well. Would this not make the thread the UI
thread or am I missing something?

Yes, that would be the UI thread. But the only method calling Sleep is
ThreadFunc, which executes in the worker thread creates by btnStart.

Jon
 

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