简体   繁体   中英

How can I lock Application.OpenForms?

I am writing a simple framework for testing WinForm apps, which is running in process. I am using Application.OpenForms to find the control I need to change in the current test step. However, I am sometimes getting errors that the collection was modified (probably by some other thread). How can I lock this collection, to make sure that the normal UI threads (opening new forms, etc) are not interfering?

I don't think there is a build-in solution to lock this property. I suggest not to use a foreach -loop in order to prevent the exception caused by the iterator finding a modified collection. Just use a while -loop (not a for -loop because the breaking condition is only evaluated once).

There is still the risk that a form is removed from the list after you checked that there is another form to be processed and you will get an ArgumentOutOfRangeException . you will have to handle this situation gracefully.

Int32 index = 0;
while (index < Application.OpenForms.Count)
{
    try
    {
        // Try to copy the form because the index may be or may
        // become invalid.
        Form form = Application.OpenForms[index];

        // Do stuff with the form.
    }
    catch (ArgumentOutOfRangeException exception)
    {
        // Handle no longer valid index.
    }

    index++;
}

This is far from perfect but I cannot think of a better solution at the moment. For example you could be at form five, get suspended, and the application closes some of the first five forms. In consequence unprocessed forms are moved towards the front and in consequence are ignored by the code when the thread continues at form six.

If you have to process all forms open at some point (like a snapshot of the application state), it might actually be the only solution to use a foreach -loop and retry until you manage to iterate over the complete collection without an exception. There is of course the obvious risk that this never happens if forms are opened and closed at very high rates or depending on your iterating code.

Forget about Application.OpenForms and use unmanaged WinApi instead.

static IEnumerable<Form> EnumOpenForms()
{
    foreach (System.Diagnostics.ProcessThread thread in System.Diagnostics.Process.GetCurrentProcess().Threads)
    {
        List<IntPtr> hWnds = new List<IntPtr>();
        EnumThreadWindows(thread.Id, (hWnd, param) => { hWnds.Add(hWnd); }, IntPtr.Zero);
        foreach(IntPtr hWnd in hWnds)
        {
            Form form = Control.FromHandle(hWnd) as Form;
            if (form != null)
            {
                yield return form;
            }
        }
    }
}

[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern bool EnumThreadWindows(int dwThreadId, EnumWindowProc lpEnumFunc, IntPtr lParam);

delegate void EnumWindowProc(IntPtr hWnd, IntPtr parameter);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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