BackgroundWorker or (Dispatcher)operation already completed when doing ReportProgress

10,681

Solution 1

Sequence of events

  1. DoWork is called
  2. DoWork puts "AllEmployees.Clear();" into the dispatcher queue
  3. DoWork completes
  4. The dispatcher sees "AllEmployees.Clear();" and starts processing that function.

I suggest using dispatcher.Invoke (which runs it immediately) on only the steps that actually have UI interaction.

Solution 2

Since your work is done on the UI thread, you should not use a BackgroundWorker at all.

Instead, you should update the progress bar directly inside the loop.

Share:
10,681
LobalOrning
Author by

LobalOrning

Updated on June 04, 2022

Comments

  • LobalOrning
    LobalOrning almost 2 years

    I use DispatcherTimer because I need to do an operation every couple of minutes. Inside this I call a BackgroundWorker to do my work, and then use the dispatcher attached to the timer to update my UI. I'm thinking the error I'm getting has to do with the timer, but I'm not sure. Is the dispatcher done or the backgroundworker? How can I do the ReportProgress inside the foreach?

    The error:

    This operation has already had OperationCompleted called on it and further calls are illegal.

    when doing this:

    (sender as BackgroundWorker).ReportProgress((counterTotalSteps / 100) * counterOnStep);
    

    Here is the simplified:

    DispatcherTimer dispTimer = new DispatcherTimer();
    Dispatcher dispatcher = dispTimer.Dispatcher;
    dispTimer.Tick +=  delegate {dispTimer_Tick(dispatcher); };
    dispTimer.Interval = new TimeSpan(0, 0, 45);
    dispTimer.Start();
    
    private void DoWork(object sender,Dispatcher dispatcher)
    {
        int counterTotalSteps = PartialEmployees.Count();
        int counterOnStep = 1;
    
        dispatcher.BeginInvoke(new Action(() =>
        {                
            AllEmployees.Clear();
            //calling the ReportProgress here works
            foreach (var item in PartialEmployees)
            {
                counterOnStep ++;
                //part below throws the error
                (sender as BackgroundWorker).ReportProgress((counterTotalSteps / 100) *      counterOnStep); 
                 AllEmployees.Add(item);                    
            }
            counterOnStep = 0;              
        }));           
    }
    

    EDIT: StackTrace:

     at System.ComponentModel.AsyncOperation.VerifyNotCompleted()
       at System.ComponentModel.AsyncOperation.Post(SendOrPostCallback d, Object arg)
       at System.ComponentModel.BackgroundWorker.ReportProgress(Int32 percentProgress, Object userState)
       at System.ComponentModel.BackgroundWorker.ReportProgress(Int32 percentProgress)
       at testDispatcher.ViewModel.EmployeeListViewModel.<>c__DisplayClass7.<DoWork>b__6() in C:\Users\kozaj\Documents\Visual Studio 2010\Projects\testDispatcher\testDispatcher\ViewModel\EmployeeListViewModel.cs:line 91
       at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
       at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
       at System.Windows.Threading.DispatcherOperation.InvokeImpl()
       at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
       at System.Threading.ExecutionContext.runTryCode(Object userData)
       at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext 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 MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
       at System.Windows.Threading.Dispatcher.InvokeImpl(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.Threading.Dispatcher.Run()
       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 testDispatcher.App.Main() in C:\Users\kozaj\Documents\Visual Studio 2010\Projects\testDispatcher\testDispatcher\obj\x86\Debug\App.g.cs:line 50
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
    
  • LobalOrning
    LobalOrning over 13 years
    Using .Invoke() worked great. What I'm doing is incrementing a progressBar, but it seems to do it all at once ( straight to 100%). When I put a breakpoint on the WorkerProgressChanged() it doesn't get called right when the foreach calls it, rather it is called after the foreach for how many ever times it got called - or it looks that way. Any suggestions?
  • LobalOrning
    LobalOrning over 13 years
    When you say that my work is being done on the UI thread, do you say that because dispTimer_Tick() is on the UI thread and so when it creates the BackgroundWorker it too is on the UI thread?
  • SLaks
    SLaks over 13 years
    @Lobal: The BackgroundWorker will run on a background thread. However, what do you think dispatcher.BeginInvoke does?
  • LobalOrning
    LobalOrning over 13 years
    Puts the action into the UI thread queue. I need to do the loop there because those lists are bound to the UI, I'm not sure how else I could "refresh" those lists. Sorry if my work is sloppy I'm a very new coder.
  • SLaks
    SLaks over 13 years
    @Lobal: Correct. So all that happens on the background thread itself is the BeginInvoke call itself. You aren't getting any good out of the BackgroundWorker.
  • SLaks
    SLaks over 13 years
    ReportProgress internally calls BeginInvoke to report the progress. The message it posts is only processed when the UI thread is free, after you finish.
  • LobalOrning
    LobalOrning over 13 years
    What should I change to actually make use of the BackgroundWorker? In my real project I will be doing a lot of List manipulation and I know I'll need to use the BackgroundWorker correctly.
  • SLaks
    SLaks over 13 years
    @Lobal: You can manipulate a copy of the list in the background thread, then bind the UI to the copy on RunWorkerCompleted.
  • LobalOrning
    LobalOrning over 13 years
    To do that wouldn't I need to reference the controls from the View in the ViewModel, which would break the mvvm pattern? I know its just a set of guidelines, but I'd like to stick to it as much as possible.
  • SLaks
    SLaks over 13 years
    @Lobal: That's the trade-off of a design pattern. Depending on how you're binding it, you could try to suppress change events while modifying the data on the background thread, then re-enable them in Completed.