Reputation: 1798
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
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