2

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
  • 107
  • 2
  • 4
  • 13
  • 1
    Why are you using a BackgroundWorker, but doing all of the work on the UI thread? – SLaks Nov 18 '10 at 17:41
  • This is just sample code, to see if I can get this to work correctly before implementing it in other things. What part would you move outside the dipatcher? Also those lists are bound to the UI. – LobalOrning Nov 18 '10 at 17:55

2 Answers2

3

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.

Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447
  • 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 Nov 18 '10 at 18:32
  • `ReportProgress` internally calls `BeginInvoke` to report the progress. The message it posts is only processed when the UI thread is free, after you finish. – SLaks Nov 18 '10 at 19:18
1

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.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 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? – LobalOrning Nov 18 '10 at 19:09
  • @Lobal: The BackgroundWorker will run on a background thread. However, what do you think `dispatcher.BeginInvoke` does? – SLaks Nov 18 '10 at 19:10
  • 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. – LobalOrning Nov 18 '10 at 19:13
  • @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 Nov 18 '10 at 19:17
  • 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. – LobalOrning Nov 18 '10 at 19:30
  • @Lobal: You can manipulate a copy of the list in the background thread, then bind the UI to the copy on `RunWorkerCompleted`. – SLaks Nov 18 '10 at 20:00
  • 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. – LobalOrning Nov 18 '10 at 20:28
  • @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`. – SLaks Nov 19 '10 at 01:15