我是c#和异步编程的新手,所以我遇到的问题的解决方案可能很简单(或不是?!)。
问题是:
- 我有一个onClick函数,创建一个新的UIControl,并将其添加到当前表单作为一个新的选项卡。
- 我让它同步运行,没有崩溃,但UI被阻塞了几秒钟,因为提供给这个标签的数据需要大量的计算。
- 我将onclick移动到异步,并在控制的数据处理功能上添加了等待,但我现在得到了臭名昭著的:
"在窗口句柄创建之前,不能在控件上调用Invoke或BeginInvoke "
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);
}
}
崩溃总是发生在我调用函数"processData"但不总是在同一个地方。我有一些调试行,它们并不总是相同的处理。
崩溃告诉我问题在"应用程序中。运行(新FormMain());">
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FormMain());
}
下面是完整的堆栈跟踪:
我到底做错了什么?System.Reflection。TargetInvocationException HResult=0x80131604
Message=异常已被调用的目标抛出。
= System.Private来源。CoreLib StackTraceSystem.RuntimeMethodHandle。调用方法(对象目标,对象[]参数,签名签名,布尔构造函数,布尔wrapExceptions)在System.Reflection.RuntimeMethodInfo。调用对象obj, BindingFlags
在System.Delegate。DynamicInvokeImpl(对象[]args)在System.Delegate。动态调用(对象[]args)System.Windows.Forms.Control.InvokeMarshaledCallbackDo (ThreadMethodEntry时间)System.Windows.Forms.Control。InvokeMarshaledCallbackHelper(对象obj)System.Threading.ExecutionContext.RunInternal (ExecutionContextexecutionContext, ContextCallback, callback, Object state)——从前一个位置结束堆栈跟踪——at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() atSystem.Threading.ExecutionContext.Run (ExecutionContextexecutionContext, ContextCallback,回调,对象状态)atSystem.Windows.Forms.Control.InvokeMarshaledCallback (ThreadMethodEntry
at System.Windows.Forms.Control.WndProc(Message&米)System.Windows.Forms.Control.ControlNativeWindow.WndProc (Message&m)
at System.Windows.Forms.NativeWindow。回调(IntPtr hWnd, WM msg)IntPtr wparam, IntPtr lparam) atInterop.User32.DispatchMessageW (MSG&味精)System.Windows.Forms.Application.ComponentManager.Interop.Mso.IMsoComponentManager.FPushMessageLoop (UIntPtrdwComponentID, msoloop uReason, Void* pvLoopData) atSystem.Windows.Forms.Application.ThreadContext.RunMessageLoopInner (msoloop原因,ApplicationContext上下文)System.Windows.Forms.Application.ThreadContext.RunMessageLoop (msoloop原因,ApplicationContext上下文)System.Windows.Forms.Application。运行(Form mainForm)Program.Main ()Program.cs:行22日这个异常最初是在这个调用栈上抛出的:(外部代码)
内部异常1:InvalidOperationException: Invoke或BeginInvoke在窗口句柄被调用之前,无法在控件上调用创建。
问题是Task.Run(() => newPanel.processData(result))
,这只能工作时,processData()没有触及任何UI的东西(但为什么它会是面板的一部分?)
你只能使用Task.Run()来处理东西,然后在主线程的UI中加载结果。
看看你能不能让它看起来像:
//await Task.Run(() => newPanel.processData(result));
var data = await Task.Run(() => ProcessData(result));
newPanel.AcceptData(data);
LoadData和ProcessData()最好是某个服务类的一部分,而不是表单的一部分。但那是关于架构的,而不是关于直接问题的。
这里的代码
TabControl newPanel = new TabControl();
newPanel.Dock = DockStyle.Fill;
// provide Data to new control (long processing)
await Task.Run(() => newPanel.processData(result));
尝试创建一个新的TabControl,但从不显示它,因此它从来没有关联一个窗口句柄。这完全独立于数据处理是同步的还是异步的。
您似乎也缺少一些相关的代码,因为System.Windows.Forms.TabControl
没有processData
方法。
我遇到的问题的核心是创建控件不会自动确保连接到它的处理程序被创建。
我改变了我的代码,首先显示面板(带有加载消息),而数据被异步处理和显示。
正如@Henk Holterman所指出的,架构可以/应该被改变和改进,以避免需要这种解决方案。