BeginInvoke 和类数组
Posted
技术标签:
【中文标题】BeginInvoke 和类数组【英文标题】:BeginInvoke and array of classes 【发布时间】:2021-06-19 05:10:32 【问题描述】:我有一个如图所示的类设置 - 我知道长时间运行的任务会更好,但这是非常精简的原始状态。我已经删除了任何似乎对问题没有必要的内容。
TLDR:
-
代码循环运行,列出的条件(1/2/3)可以独立执行
日志显示
condition3
仅在currentParentRun.ChildRuns.Count
> 0 时才会执行
错误被抛出并被捕获,表明(连同其他日志)在 BeginInvoke 的调用函数中,计数 >0 的 currentParentRun.ChildRuns[currentParentRun.ChildRuns.Count - 1]
以某种方式超出了范围。
// Class created and owned by UI
public class DataProcessor
private BackgroundWorker worker = new BackgroundWorker();
private ParentRun currentParentRun = null;
public DataProcessor()
// register background worker events
worker.WorkerSupportsCancellation = false;
worker.WorkerReportsProgress = false;
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
private void worker_DoWork(object sender, DoWorkEventArgs e)
while (process)
try
// Create a new parent
if (condition1)
currentParentRun = new ParentRun();
// Create a new child (parent will always be not null here)
if (condition2)
ChildRun childRun = new ChildRun();
currentParentRun.ChildRuns.Add(childRun);
// Call the UI and update
if (condition3)
ShowFinishedChildUI();
System.Threading.Thread.Sleep(loopDelayProcessor);
catch (Exception ex)
public void ShowFinishedChildUI()
System.Windows.Application.Current.Dispatcher.BeginInvoke((Action)(() => ShowFinishedChildUIDelegate()));
public void ShowFinishedChildUIDelegate()
// Parent is the UI being updated
parent.ShowFinishedChild(currentParentRun.ParentRunID,
currentParentRun.ChildRuns[currentParentRun.ChildRuns.Count - 1].ChildRunID);
resetParentDisplay = false;
这里,“父”是拥有的 UI 控件,我们使用 BeginInvoke 发送调用 UI 函数来更新。
每 20-30k 个新的“父”创建偶尔会发生什么,而在处理过程中,Index was out of range. Must be non-negative and less than the size of the collection.
的 ShowFinishedChildUIDelegate
函数出现错误据我所知,它是 总是至少非零,这让我认为在同一行代码之间的某个地方,孩子的数量在之间变化
currentParentRun.ChildRuns[...]
和
currentParentRun.ChildRuns.Count - 1
是否有可能如果在委托处理时主代码块中的condition2
得到满足,那么同一行代码可以有不同的值,其中子代的数量大于类的数量列表?这是我的最佳猜测,但我不确定如何验证。
谢谢。
更详细的错误:
2021-03-19 12:00:12.8174|ERROR|GeneralLogger|UnhandledException StackTrace : at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.Collections.Generic.List`1.get_Item(Int32 index)
at QASystem.Classes.DataProcessor.ShowFinishedChildUIDelegate() in D:\..\QASystem\QASystem\Classes\DataProcessor.cs:line 756
at QASystem.Classes.DataProcessor.<ShowFinishedChildUI>b__76_0() in D:\..\QASystem\QASystem\Classes\DataProcessor.cs:line 751
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at QASystem.App.Main()
【问题讨论】:
标记您的 GUI:WPF、WinForms 等? 避免空的 Try-Catches。它在向你隐瞒问题。在不知道“条件#”是什么意思的情况下,您的代码可能使condition2
为假但condition3
为真,因此它可能会抛出该异常。但我们没有看到该代码。
我删除了 try catch 以使代码简洁,它记录了超出范围的错误。 Condition3
可以在没有 condition2
的情况下执行 - 这是有效的 - 问题是当 .Count
> 0 时,这 (currentParentRun.ChildRuns[currentParentRun.ChildRuns.Count - 1]
) 会给我一个索引超出范围异常,这似乎是不可能的。
作为旁注,BackgroundWorker
类具有事件ProgressChanged
和RunWorkerCompleted
,您应该使用它们在处理期间和之后与 UI 进行交互。使用Dispatcher
与UI 通信首先否定了使用BackgroundWorker
的任何优势。您可以手动启动Thread
,这不会有任何区别。顺便说一句,BackgroundWorker
是technologically obsolete 恕我直言。 Async/await 让它变得无关紧要。
谢谢,很高兴知道!是的,退出BackgroundWorker
是长期目标之一——这是继承的,它是一个缓慢的新基地:)
【参考方案1】:
一个可能的猜测是代码不是线程安全的。该示例根本不包含同步。例如,代码可以按以下顺序运行:
-
线程A:
var count = currentParentRun.ChildRuns.Count - 1;
线程 B:currentParentRun = new ParentRun();
线程A:currentParentRun.ChildRuns[count];
如您所见,这可能会导致错误的、线程不安全的行为。
解决此问题的一种方法是在 UI 更新时插入锁以防止修改。另一种方法是创建数据的副本并将此副本提供给 UI,从而防止多个线程访问相同数据的风险。即
var parentId = currentParentRun.ParentRunID;
var childId = currentParentRun.ChildRuns[currentParentRun.ChildRuns.Count - 1].ChildRunID;
Dispatcher.BeginInvoke((Action)(() => ShowFinishedChildUIDelegate(parentId, childId)));
【讨论】:
谢谢!您的代码块实际上是我已经开始实施的,我很高兴这对您来说很突出。这绝对似乎是一个线程问题,但我从未见过像currentParentRun.ChildRuns[currentParentRun.ChildRuns.Count - 1]
这样的问题与 > 0 个元素有关。我也会研究更好的线程安全访问。以上是关于BeginInvoke 和类数组的主要内容,如果未能解决你的问题,请参考以下文章