I'm new to C# and async programming so the solution to the problem I have might be simple (or not?!).
Here is the issue:
- I have an onClick function that create a new UIControl and add it to the current Form as a new tab.
- I had it run synchronously first and had no crash but the UI was blocked for a few second as the data provided to this Tab require a lots of calculations.
- I moved the onclick to async and added an await on the Control's datprocessing function but I now get the infamous:
"Invoke or BeginInvoke cannot be called on a control until the window handle has been created".
private async void btnNewTab_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Multiselect = true;
openFileDialog.Filter = "*.test";
DialogResult dialogResult = openFileDialog.ShowDialog();
if (dialogResult == DialogResult.OK)
{
//Display a basic loading Control while the other control is being prepared
TabPage myTabPage = new TabPage("loading");
LoadingControlPanel loadingPanel = new LoadingControlPanel();
loadingPanel.Dock = DockStyle.Fill;
myTabPage.Controls.Add(loadingPanel);
tabControl.TabPages.Add(myTabPage);
tabControl.SelectTab(myTabPage);
//Load Data from files
List<DataFile> result = await Task.Run(() => loadFiles(openFileDialog.FileNames));
// create Control to display the data loaded
MyTabControl newPanel = new MyTabControl();
newPanel.Dock = DockStyle.Fill;
// provide Data to new control (long processing)
await Task.Run(() => newPanel.processData(result));
// rename Tab, remove loading Control and display the tab with the processed Data
myTabPage.Text = "Loaded";
myTabPage.Controls.Remove(loadingPanel);
myTabPage.Controls.Add(newPanel);
}
}
The crash always happen while I call the function "processData" but not alway at the same location. I have a few debug lines and they are not always the same processed.
The crash tells me the issue in on the line: "Application.Run(new FormMain());"
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FormMain());
}
Here is the full stack trace:
System.Reflection.TargetInvocationException HResult=0x80131604
Message=Exception has been thrown by the target of an invocation.
Source=System.Private.CoreLib StackTrace: at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Delegate.DynamicInvokeImpl(Object[] args) at System.Delegate.DynamicInvoke(Object[] args) at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme) at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme) at System.Windows.Forms.Control.InvokeMarshaledCallbacks()
at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam) at Interop.User32.DispatchMessageW(MSG& msg) at System.Windows.Forms.Application.ComponentManager.Interop.Mso.IMsoComponentManager.FPushMessageLoop(UIntPtr dwComponentID, msoloop uReason, Void* pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(msoloop reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(msoloop reason, ApplicationContext context) at System.Windows.Forms.Application.Run(Form mainForm) at Program.Main() in Program.cs:line 22This exception was originally thrown at this call stack: [External Code]
Inner Exception 1: InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
What on earth am I doing wrong with all this?
CodePudding user response:
The problem is Task.Run(() => newPanel.processData(result))
, that could only work when processData() didn't touch any UI stuff (but then why would it be part of a panel?)
You can only use Task.Run() on processing stuff and then load the results in the UI on the main thread.
See if you can make it look like:
//await Task.Run(() => newPanel.processData(result));
var data = await Task.Run(() => ProcessData(result));
newPanel.AcceptData(data);
LoadData and ProcessData() should preferably be part of some service class, not your Form. But that is about architecture, not about the direct problem.
CodePudding user response:
This code here
TabControl newPanel = new TabControl();
newPanel.Dock = DockStyle.Fill;
// provide Data to new control (long processing)
await Task.Run(() => newPanel.processData(result));
attempts to create a new TabControl, but never shows it, therefore it never has a window handle associated. This is completely independent of whether you do your data processing synchronously or asynchronously.
You seem to be missing some relevant code there as well, as System.Windows.Forms.TabControl
does not have a processData
method.