BackgroundWorker "Cross-thread operation not valid..."

R

Rob R. Ainscough

I'm using a BackgroundWorker to perform a file download from an ftp site.
Per good code design practices where I separate my UI code from my core
logic code (in this case my Download file method in my FileIO class) I've
established Public Event in my core logic classes along with RaiseEvents
(that will updated a progress bar on the UI side). This all works great
when I'm NOT using Threading (BackgroundWorker), however, as soon as I
introduce threading and fire off the download via a BackgroundWorker
control, I get the above error whenever I issue an RaiseEvents in my core
code.

I've gone thru the many samples and noticed that none of them separate UI
code from logic code (perhaps to save time for purpose of demonstration).
So it appears that the simple approach of using BackGroundWorker is once
again not very useful for anyone implementing more professional code
design/solutions.

It would appear to me that if I want this to work correctly, I'd have to
pass to my core logic the background worker control? Not an option. Or,
setup a public event for the BackgroundWorker control and change my raise
event to reference the event that will trigger the background worker?

I must admit, I'm not sure why this limitation exists?


Rob
 
R

Rob R. Ainscough

Brendan,

Good article, but it was written before .NET 2.0 was released. I'm using
the new BackgroundWork approach (in .NET 2.0) to threading as it implements
much cleaner and more manageable code than Invoke approach.

Still testing out some solutions, but I suspect I might be able to use
WithEvents on the BackgroudWorker defined in my class modules -- it also
appears that BackgroundWorker is part of system.componentmodel so I can
conceptually use it in my non-UI classes effectively retaining separation of
UI code.

Rob.
 
D

Dave Sexton

Hi Rob,

You must use Control.Invoke to invoke code on the UI thread, which is the
thread that is running the message-loop, when you want to update the display
such as a progress bar. If you call a method on a Control from a different
Thread you will get an exception in the 2.0 framework.
Control.InvokeRequired is used to determine whether the code is calling from
another Thread, and if so Control.Invoke should be used. Control invocation
on the UI thread has nothing to do with the BackgroundWorker and is required
no matter the type of asynchronous architecture you implement.

// ctrl is assumed to be an instance of a Control or a derived Type

private void AsyncMethod()
{
if (ctrl.InvokeRequired)
{
ctrl.Invoke(new MethodInvoker(ctrl.Invalidate));
}
else
{
ctrl.Invalidate();
}
}

HTH
 
R

Rob R. Ainscough

Dave,

I've discovered that I can't access any controls from within the thread --
at first this tossed me for a loop (and I'm still not convinced this is a
good implementation by .NET 2.0 since the thread is still under the scope of
the calling source -- I'm using BackgroundWorker). However, I was
determine to keep my non-UI core classes (i.e. FileIO.Download) working for
any usage that might be tossed at it.

I was able to come up with a WithEvents and RaiseEvent approach in my non-UI
classes that would eventually make it to the UI handler which would call the
BackgroundWorker.ReportProgress. I created my own userState object class
(again non-UI) that could be used to provide any additional information to
the ProgressChanged.

In the core of the file download code I issue a raise event (using a ByRef
on boolean) after each byte stream is received that checks the state of the
BackgroundWorker.CancellationPending which is return in my local method's
boolean variable. I check to see if true and then exit and close the
stream, if not continues processing. So the class can handle both being
threaded and non-thread implementations.

Rob.
 
B

Brendan Green

Hmmm...

Here is some sample code that I'm using right now (uses
BackgroundWorkerThread and populates a listview for event logging):

BackgroundWorker m_eventWorker = new BackgroundWorker();

// This delegate enables asynchronous calls for adding items
// to a ListView control.
delegate void AddEventToListCallback(ListViewItem lvi);

....

public frmConsole()
{
InitializeComponent();
Helper.RunRemotingServices();

m_eventWorker.DoWork += new DoWorkEventHandler(m_eventWorker_DoWork);
}

private void frmConsole_Load(object sender, EventArgs e)
{
m_eventWorker.RunWorkerAsync();
}

void m_eventWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
if (Fenestra.Foundation.Core.EventQueue.Events.Count > 0)
{
Fenestra.Foundation.Core.Event et =
Fenestra.Foundation.Core.EventQueue.Events.Dequeue();
ListViewItem i = new ListViewItem(new string[] {
et.Timestamp.ToString("dd/MM @ HH:mm:ss.mm"), et.Data });
i.Tag = et;

this.AddEventToList(i);
}

System.Threading.Thread.Sleep(new TimeSpan(0, 0, 5));
}
}


private void AddEventToList(ListViewItem lvi)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.lvEvents.InvokeRequired)
{
AddEventToListCallback d = new AddEventToListCallback(AddEventToList);
this.Invoke(d, new object[] { lvi });
}
else
{
this.lvEvents.Items.Add(lvi);
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top