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 类具有事件ProgressChangedRunWorkerCompleted,您应该使用它们在处理期间和之后与 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 和类数组的主要内容,如果未能解决你的问题,请参考以下文章

invoke和beginInvoke

BeginInvoke 调用的同步

invoke和begininvoke 区别

markdown 数组,对象和类数组对象

C#中的invoke和begininvoke

Invoke 与 BeginInvoke的区别