0

I have a winforms application where I run a BackgroundWorker before the Application.Run of the main Form.

When the BackgroundWorker is finished, in its RunWorkerCompleted handler - it accesses the main Form, and I get the exception:

"Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on."

So I thought the error had to do with this comment which states that RunWorkerCompleted

"will only get raised on the UI thread if the UI thread created the BGW instance."

(though it doesn't seem like it's created on a separate thread). (And see this comment there too).

So I created a simple test where I BW.RunWorkerAsync(); before Application.Run (In "Program") and it works fine there. No exception thrown.

So what might be the problem? Why does interacting with the main Form throw an exception though I am running the BackgroundWorker from the same thread?

(I can't post the whole code here because it's very long. And posting just the relevant code is what I mentioned earlier - it does not throw an exception.)

EDIT

So perhaps more concrete questions might be in place: How does one make the "UI thread create the BGW"? Does it have to be inside an Application.Run? After a Form is shown? Does it perhaps not depend on which thread created the BGW, but which thread calls RunWorkerAsync?

EDIT 2

Checking the Thread.CurrentThread.ManagedThreadId I saw that it's 8 before RunWorkerAsync (as it is before DoWork += and RunWorkerCompleted += ) but 9 inside the RunWorkerCompleted handler.

When stepping through the code and waiting after RunWorkerAsync() for a couple of seconds - they all have the same thread ID and it runs fine consistently (so not just by chance that the correct thread was chosen)!

Community
  • 1
  • 1
ispiro
  • 26,556
  • 38
  • 136
  • 291
  • Have you tried to invoke the form? (Also, are you looking for a solution or an explanation) – Sayse Aug 29 '14 at 10:21
  • I'm trying to understand it better so I can fix it up to not _need_ `Invoke` (and avoid more pitfalls in the future). – ispiro Aug 29 '14 at 10:22
  • 7
    *I can't post the whole code here because it's very long. And posting just the relevant code is what I mentioned earlier - it does not throw an exception* Then how do you expect us to answer? Even if we do it will be purely a **guess** without some code which reproduces the problem. – Sriram Sakthivel Aug 29 '14 at 10:25
  • I figured as much, it sounds like your `Application.Run` has dependancies on the background worker anyway (for initializing the form?) so it may just be an option to include this run into the background worker but then as you said this may just patch up an issue. It really is guess work without an example displaying the problem. – Sayse Aug 29 '14 at 10:27
  • Unfortunately, I can't see what else I can do here - post hundreds of lines of code? Impractical. Someone who knows a subject well, knows what are the logical mistakes one makes in his field. I've seen that in winforms, for example. I had hoped that that would be the case here - that an expert would easily recognize what I'm doing wrong by what I've wrote. – ispiro Aug 29 '14 at 11:14
  • I am not one to second-guess Hans Passant, but as far as I can tell the official documentation for BackgroundWorker makes no promises as to which thread the completion event runs on, so I would guard against that by using Invoke as appropriate. – 500 - Internal Server Error Aug 29 '14 at 11:28
  • @500-InternalServerError Thanks. That's actually a good idea. (Though, I would still like to understand what's happening in my code.) – ispiro Aug 29 '14 at 11:32
  • Sorry, I don't understand how do you generate an exception. Please, see my example. The same can be done from inside form events e.t.c. Of course if you create BGW from some other thread it will fail with an exception. – norekhov Aug 29 '14 at 11:50

3 Answers3

2

Strong hint that you are calling RunWorkerAsync() from the wrong thread. The BGW needs to figure out which particular thread it runs its events on. It cannot do that by itself, it needs help. You can simply add some diagnostic code to your program to verify that this help is provided:

public static class DebugUtils {
    public static void CheckThreadState() {
        if (System.Threading.SynchronizationContext.Current == null) {
            throw new InvalidOperationException("You are on the wrong thread")
        }
    }
}

And insert this call in all places of your code where you call RunWorkerAsync():

DebugUtils.CheckThreadState();
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
0

Here's an example which works:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Form1 form = null;
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            BackgroundWorker workerThread = new BackgroundWorker();
            workerThread.DoWork += delegate
            {
                Thread.Sleep(1500);
            };
            workerThread.RunWorkerCompleted += delegate
            {
                if ( form != null )
                    form.BackColor = Color.Red;
            };
            workerThread.RunWorkerAsync();

            form = new Form1();
            Application.Run(form);
        }

    }
}

Ok, it has some race problems. But i don't see any problem. Run and main thread are the same UI thread - you can check it in debug. How to make an exception?

norekhov
  • 3,915
  • 25
  • 45
  • This will not run the completed event in the UI thread. – Servy Aug 29 '14 at 14:06
  • Ridiculous. Try to run and debug it and you will see that it's actually an UI thread. Also there's no exception, why? Becuase it's UI thread! ) – norekhov Aug 30 '14 at 18:45
  • No, it won't, because there's not current synchronization context when you're starting the worker for it to capture, so it'll fire the completed event in the background thread. – Servy Sep 02 '14 at 13:48
0

BGW can't just magically run code in the UI thread. It needs to have some mechanism through which it knows what the UI thread is so that it can marshal event handlers there.

What it actually uses is SynchronizationContext.Current. It "remembers" what the current context is when you callRunWorkerAsync and then uses that context to marshal event handlers. It is the call to Application.Run that is creating the synchronization context for your UI's message loop, so since you're starting the worker before you even have a message loop or a sync context, it has no way of marshaling code there.

You need to wait to start the background worker until you actually have your message loop. One simple way of doing this is to start it in an event of your form that won't be fired until the message loop has been set up, such as the Load event:

form.Load += (s, args) => worker.RunWorkerAsync();
Servy
  • 202,030
  • 26
  • 332
  • 449
  • This sounds logical. However, as I've said - I tried a simple example like that - running `RunWorkerAsync();` before `Application.Run` and it runs fine. – ispiro Aug 29 '14 at 14:19
  • @ispiro Any number of things could be going on. Without actually knowing what you did, there isn't much to be said. You could be suppressing the exception without actually marshaling to the UI thread, you could not be modifying a UI object when you thought you were, you could have been setting things up in a way that did actually create a context before you thought you did, etc. – Servy Aug 29 '14 at 14:22