Kevin
Kevin

Reputation: 1798

InvokeRequired returns false when the form has been created

This question is about how to get execution back on the UI thread - InvokeRequired is returning false when I think it should be returning true. I have a simple Windows Forms app for testing and Debug.Writeline messages to the output window in VStudio to track the threads and actions.

My goal (or app architecture) is to have all action initiations (menu or other callers/initiators) go through a single interface layer that provides async/await services to the calling threads so they can be freed up while the interface worker threads and methods do the work.

The problem is that the InvokeRequired test returns false. When I skip the test and just call Invoke((Action) FormCloseDutiesHelper), the error message says that the handle to the control (I think Form1) has not been created. But of course, Form1 has been created - I'm clicking on the File/Close menu item!

I have read a dozen SO posts on InvokeRequired and ways of optimizing the code pattern, but I have found nothing that explains why InvokeRequired thinks "the" control does not have a handle. Mousing over "this.InvokeRequired" says that "this" is the MyDemoProgram.Form1 object. I am at a loss to explain what is going on.

Here is the test code I'm using, from the menu click code onward.

Program.cs:
 internal static class Program
    {
      public static readonly Form1 PublicForm1 = new();

Form1.cs:
  public partial class Form1 : Form
  {
  
  public Form1() {
    InitializeComponent();
  }

  async void closeToolStripMenuItem_Click(object sender, EventArgs e) {
    var me = "CloseMenuItemClick";
    var msg = $"{me} Thread id {Thread.CurrentThread.ManagedThreadId}";
    Debug.WriteLine(msg);

    // this frees the UI thread up for other work - and it does, as shown by the trace below
    await TaskHelperClose();
  }


Interface.cs:
      public static async Task<bool> TaskHelperClose() {
        var me = "TaskHelperClose";
        var msg = $"{me} Thread id {Thread.CurrentThread.ManagedThreadId}";
        Debug.WriteLine(msg);

        // this lets the calling thread go away immediately
        await Task.Run(HelperClose);

        // just so that the Task does not return void
        return true;
      }


Interface.cs:
  // this runs on the new worker thread
  internal static void HelperClose() {
    var me = MethodBase.GetCurrentMethod()?.Name;
    var msg = $"{me} Thread id {Thread.CurrentThread.ManagedThreadId}";
    Debug.WriteLine(msg);

    // close means put the window in the tray
    // FormCloseCaller tests InvokeRequired to get the work done on the UI thread
    PublicForm1.FormCloseCaller();
  }

Form1.cs:
  // this is still running on the worker thread
  internal void FormCloseCaller() {
    var me = MethodBase.GetCurrentMethod()?.Name;
    var msg = $"{me} Thread id {Thread.CurrentThread.ManagedThreadId}";
    Debug.WriteLine(msg);

    // this is supposed to return true for the menu I just clicked on, but it does not.
    if (this.InvokeRequired) {
      Debug.Writeline($"{me} Invoke required.");
      Invoke((Action) FormCloseDutiesHelper);
    }
    else {
      Debug.Writeline($"{me} Invoke NOT required.");
      FormCloseDutiesHelper();
    }
  }


Form1.cs:
 void FormCloseDutiesHelper() {
    var me = MethodBase.GetCurrentMethod()?.Name;
    var msg = $"{me} Thread id {Thread.CurrentThread.ManagedThreadId}";
    Debug.WriteLine(msg);

    msg = $"{me} Thread id {Thread.CurrentThread.ManagedThreadId}" 
          + " Setting window state to minimized";
    Debug.WriteLine(msg);

    WindowState = FormWindowState.Minimized;
    ShowInTaskbar = false;

    notifyIcon1.Text = @"Click or double click to open";
    notifyIcon1.Visible = true;
  }

Here is the trace of the threads in execution. The UI thread is 1, the await/async/worker thread is 9, and Invoke is NOT required (even though the worker thread is not the UI thread), and the form is not minimized.

CloseMenuItemClick Thread id 1 - the UI thread
TaskHelperClose Thread id 1

HelperClose Thread id 9        - the await Task.Run(HelperClose) thread
FormCloseCaller Thread id 9    - this guy tests InvokeRequired
FormCloseCaller Invoke NOT required. - and concludes that Invoke is NOT required

FormCloseDutiesHelper Thread id 9  - and used the worker thread to call the helper code
FormCloseDutiesHelper Thread id 9 Setting window state to minimized
- and the window was not minimized.

I do not get cross-thread exceptions, either, except when I skip the InvokeRequired test and Invoke the FormCloseDutiesHelper method directly.

Can anyone explain what's going on and how I can get the work done on the original UI thread? Thank you.

Upvotes: 0

Views: 125

Answers (1)

Kevin
Kevin

Reputation: 1798

As @KlausGutter suggested, I did in fact have TWO forms in my program. I found the problem at exactly the same time he posted his answer. Here is the offending code. I use PublicForm1 to make the form visible to other classes, and when I defined it I forgot to put the name in the Application.Run(new Form1()); statement shown below. Once I put PublicForm1 into the Run statement, everything started to work as expected.

  internal static class Program
    {
      public static readonly Form1 PublicForm1 = new();

        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
          
            // To customize application configuration such as set high DPI settings or default font,
            // see https://aka.ms/applicationconfiguration.
            ApplicationConfiguration.Initialize();
            Application.Run(new Form1());

Upvotes: 0

Related Questions