deadlock when using waitOne in a STA thread

D

Daniel Cuculescu

Hi,

I have the problem described bellow with the calls to WaitHandle.WaitOne
causing re-entrancy on main GUI thread (STA) of a .NET application and I
would like to find out:
- did anybody else run into the same problem?
- is it a known issue? is there a fix for it?
- is there a workaround for it?
- if no fix/workaround I would like to find out the complete list of
messages that are being handled (dispatched by ole32.dll OLE/COM message
pump) when doing WaitOne on a STA thread.

The problem is:

I have 2 (or more) calls pending to execute on the main gui thread (I am
actually doing Invoke on some control)
the first call, at some point does WaitHandle.WaitOne.
at this point, if a Windows message comes to the app (like
WM_POPUPSYSTEMMENU - 0x0313 ) triggered by a right-click on the task bar
icon of the app the following happens:
- the app processes this message
- the app starts processing other pending messages - like my second
invoke
- at this point my app is deadlocked, because it is not supposed to
enter the second call before finishing processing the first one.
- also at this point the entire system is not behaving properly - for
example I cannot bring up any window by clicking on the taskbar icon
important note: if I would not do right click on the taskbar icon of my app,
and just let it work in background, everything would be ok.

After investigating this issue for a while, I found that WaitOne is not
actually a blocking call when called from an STA thread.

The only reference I found related to this matter in MSDN pages is here:
http://msdn.microsoft.com/en-us/library/74169f59.aspx .
Quoting from that page:
"WaitHandle..::.WaitOne,WaitHandle..::.WaitAny, WaitHandle..::.WaitAll,
Monitor..::.Enter, Monitor..::.TryEnter, Thread..::.Join,
GC..::.WaitForPendingFinalizers, and so on are all responsive to
Thread..::.Interrupt and to Thread..::.Abort. Also, if your thread is in a
single-threaded apartment, all these managed blocking operations will
correctly pump messages in your apartment while your thread is blocked."

If the apartment state is STA this function actually creates a message loop
that is processing some events (for now I have been able to identify 2 of
them (WM_POPUPSYSTEMMENU and WM_ACTIVATEAPP). The problem is that these
messages triggers processing of other messages in the queue (like the
registered messages used for invoke in .NET ).

I have attached a simplified application to demonstrate the issue. In this
app I have created a thread from where I am calling multiple times the same
function which is suppose to execute on the GUI thread.
I have put some console prints to show that it is executing another invoke
on the main thread even if it is supposed to stay in the wait.

Another thing, in my app the calls to some functions are not supposed to
re-enter (and we are protecting them with the waitOnes) but if I modified
the sample app (attached to this mail) to have a non-blocking re-entrancy ,
I noticed if I right click on the taskbar icon while the first call is in
waitOne, all the other calls will be executed before the first call ends -
this is actually easy to explain since the other calls are not processed by
the app message loop but they are processed by the message loop created by
the WaitOne. however I don't think this should be the normal behavior - that
is: when I invoke 3 calls on some thread, the order of execution should be
the order of the invokes.

waiting forward for your replies
Daniel.
 
D

Daniel Cuculescu

at this point changing the entire app like you say is not an option. (it's a
huge app, many modules)

but here is another aspect of the same problem
when i simply let the app run without any intervention, i get the folowing
output:

Entering... 1
Entering... 2
Entering... 3
Entering... 4
Entering... 5
Entering... 1
Entered 1 - 1 time
Doing something meaningfull for call #1
Exited 1 - 1 time
Entering... 2
Entered 2 - 1 time
Doing something meaningfull for call #2
Exited 2 - 1 time
Entering... 3
Entered 3 - 1 time
Doing something meaningfull for call #3
Exited 3 - 1 time
Entering... 4
Entered 4 - 1 time
Doing something meaningfull for call #4
Exited 4 - 1 time
Entering... 5
Entered 5 - 1 time
Doing something meaningfull for call #5
Exited 5 - 1 time

if while waiting in the first invoke i right click on the taskbar icon of
the app i get this (please notice the order the invokes get executed - the
order of messages 'Doing something meaningfull for call #X'):

Entering... 1
Entering... 2
Entering... 3
Entering... 4
Entering... 5
Entering... 1
Entered 1 - 1 time
Entering... 2
Entered 2 - 2 time
Doing something meaningfull for call #2
Exited 2 - 2 time
Entering... 3
Entered 3 - 2 time
Doing something meaningfull for call #3
Exited 3 - 2 time
Entering... 4
Entered 4 - 2 time
Doing something meaningfull for call #4
Exited 4 - 2 time
Entering... 5
Entered 5 - 2 time
Doing something meaningfull for call #5
Exited 5 - 2 time
Doing something meaningfull for call #1
Exited 1 - 1 time

also notice that when i right click on the taskbar icon while the STA thread
is in the wait one, the seccond invoke starts executing before the first has
finished:

.......
Entering... 1
Entered 1 - 1 time
Entering... 2
Entered 2 - 2 time
Doing something meaningfull for call #2
Exited 2 - 2 time
.......
 
D

Daniel Cuculescu

posting the code in the attachment
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace DeadLockWaitOne
{
public partial class Form1 : Form
{
#region Designer
private Button button1;

public Form1()
{
InitializeComponent();
}

private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(86, 31);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new
System.EventHandler(this.button1_Click);
//
// Form1
//
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.ResumeLayout(false);

}
#endregion Designer


#region My Code
private void button1_Click(object sender, EventArgs e)
{
System.Threading.Thread t = new
System.Threading.Thread(delegate()
{
test(1);
test(2);
test(3);
test(4);
test(5);
}
);
t.IsBackground = true;
t.Start();
}

private delegate void TestDelegate(int callNo);
private static int enterCount = 0;
private void test(int callNo)
{
Console.WriteLine("Entering... {0}", callNo);
if ( this.InvokeRequired )
{
BeginInvoke(new TestDelegate(test), callNo);
return;
}

int enterCountValue = Interlocked.Increment(ref enterCount);
Console.WriteLine("Entered {0} - {1} time", callNo,
enterCountValue);

System.Threading.AutoResetEvent ev = new
System.Threading.AutoResetEvent(false);

// if you comment out the next line you will see that the order
of execution changes - it becomes 2,3,4,5,1
// i've put the if here to simulate the situation i have in my
app.
//if ( enterCountValue == 1 )
{
System.Threading.Thread t = new
System.Threading.Thread(delegate()
{
Thread.Sleep(10000);
ev.Set();
});
t.IsBackground = true;
t.Start();
}
ev.WaitOne();

Console.WriteLine("Doing something meaningfull for call #{0}",
callNo);

Console.WriteLine("Exited {0} - {1} time", callNo,
enterCountValue);
enterCountValue = Interlocked.Decrement(ref enterCount);
}
#endregion My Code
}


public class Program
{
[STAThread]
public static void Main(string[] args)
{
System.Windows.Forms.Application.Run(new Form1());
}
}

}
 
B

Brian Gideon

posting the code in the attachment
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace DeadLockWaitOne
{
    public partial class Form1 : Form
    {
        #region Designer
        private Button button1;

        public Form1()
        {
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // button1
            //
            this.button1.Location = new System.Drawing.Point(12, 12);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(86, 31);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new
System.EventHandler(this.button1_Click);
            //
            // Form1
            //
            this.ClientSize = new System.Drawing.Size(292, 273);
            this.Controls.Add(this.button1);
            this.Name = "Form1";
            this.ResumeLayout(false);

        }
        #endregion Designer

        #region My Code
        private void button1_Click(object sender, EventArgs e)
        {
            System.Threading.Thread t = new
System.Threading.Thread(delegate()
            {
                test(1);
                test(2);
                test(3);
                test(4);
                test(5);
            }
            );
            t.IsBackground = true;
            t.Start();
        }

        private delegate void TestDelegate(int callNo);
        private static int enterCount = 0;
        private void test(int callNo)
        {
            Console.WriteLine("Entering... {0}", callNo);
            if ( this.InvokeRequired )
            {
                BeginInvoke(new TestDelegate(test), callNo);
                return;
            }

            int enterCountValue = Interlocked.Increment(ref enterCount);
            Console.WriteLine("Entered {0} - {1} time", callNo,
enterCountValue);

            System.Threading.AutoResetEvent ev = new
System.Threading.AutoResetEvent(false);

            // if you comment out the next line you will see that the order
of execution changes - it becomes 2,3,4,5,1
            // i've put the if here to simulate the situation i have in my
app.
            //if ( enterCountValue == 1 )
            {
                System.Threading.Thread t = new
System.Threading.Thread(delegate()
                {
                    Thread.Sleep(10000);
                    ev.Set();
                });
                t.IsBackground = true;
                t.Start();
            }
            ev.WaitOne();

            Console.WriteLine("Doing something meaningfull forcall #{0}",
callNo);

            Console.WriteLine("Exited {0} - {1} time", callNo,
enterCountValue);
            enterCountValue = Interlocked.Decrement(ref enterCount);
        }
        #endregion My Code
    }

    public class Program
    {
        [STAThread]
        public static void Main(string[] args)
        {
            System.Windows.Forms.Application.Run(new Form1());
        }
    }



}

WaitOne does indeed pump messages. That is by design and that is the
crux of the matter. If that is causing undesirable behavior then you
may be left with no other choice than to change the architecture of
your application. Also, be careful about how you use WaitOne from the
GUI thread. It can lead to a deadlock especially when called from
within a synchronized block of code.
 

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