Threading fun! User controls with multithreading.

G

Guest

I'm having some difficulty getting controls to work with multithreading.
Basically, I want to begin a long running operation on another thread when a
button is clicked, and then disable that button until the operation
completes. When the operation finishes, I want the button to re-enable. To
make things more interesting, this button is located on a TabPage in a
TabControl on the main Form, so it's three containers deep. I'm using C# 2.0
Beta 2. Here's basically what my code looks like:

// Button clicked event handler
public void btnRun_click(/*params*/)
{
btnRun.enabled = false;
Thread query = new Thread(new ThreadStart(operation));
query.start();
}

private void operation()
{
/* Do a long running SQL query */
this.Parent.Parent.Parent.BeginInvoke(done());
}

private Delegate done()
{
btnRun.enabled = true;
}

When I run this code, btnRun.enabled = true throws an exception saying that
I can't access the Button from a different thread than it was created on.
However, I was under the impression that that whole point of
Control.BeginInvoke was to run a method on the same thread that the control
was created on. I've tried this using the BeginInvoke method of the Form,
the TabControl, and the TabPage, and get the same result on all three.
Anyone know how to resolve this? Thanks!
 
J

Jon Skeet [C# MVP]

Warren said:
I'm having some difficulty getting controls to work with multithreading.
Basically, I want to begin a long running operation on another thread when a
button is clicked, and then disable that button until the operation
completes. When the operation finishes, I want the button to re-enable. To
make things more interesting, this button is located on a TabPage in a
TabControl on the main Form, so it's three containers deep. I'm using C# 2.0
Beta 2. Here's basically what my code looks like:

// Button clicked event handler
public void btnRun_click(/*params*/)
{
btnRun.enabled = false;
Thread query = new Thread(new ThreadStart(operation));
query.start();
}

private void operation()
{
/* Do a long running SQL query */
this.Parent.Parent.Parent.BeginInvoke(done());
}

private Delegate done()
{
btnRun.enabled = true;
}

When I run this code, btnRun.enabled = true throws an exception saying that
I can't access the Button from a different thread than it was created on.
However, I was under the impression that that whole point of
Control.BeginInvoke was to run a method on the same thread that the control
was created on. I've tried this using the BeginInvoke method of the Form,
the TabControl, and the TabPage, and get the same result on all three.
Anyone know how to resolve this? Thanks!

That certainly looks fairly odd. Could you post a short but complete
program which demonstrates the problem?

See http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.
 
G

Guest

Ok, here goes. I understand controls can only be accessed by the thread that
created them. I thought that a container's BeginInvoke method would force
the delegate passed to it to execute asynchronously on said thread.

Below, I create a Label and add it to my Form. I then create a new thread
running the method RunThread. RunThread sleeps for 1 second (simulating work
being done), and then uses the Form’s BeginInvoke method to call the
ThreadFinished method. ThreadFinished updates the Label to reflect the
program’s status. I believe the ThreadFinished method should be running on
the same thread that created the Form because I used the Form’s BeginInvoke
method. However, I instead get an AccessViolationException at the line that
updates the Label’s text telling me that I can only access controls on the
thread that created them. Can anyone tell me why this is happening? I’m
using C# 2.0 Beta 2.

I just created a blank project and added refrences to System.Windows.Forms
and System.Drawing. This is the only code file in the project.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;

namespace Project1
{
class Class1 : Form
{
private Label lblStatus;

public static void Main()
{
Application.Run(new Class1());
}

public Class1()
{
lblStatus = new Label();
lblStatus.Text = "Before thread";
lblStatus.Location = new Point(50, 50);
this.Controls.Add(lblStatus);

this.Size = new Size(200, 200);
this.Visible = true;
this.Show();

Thread t = new Thread(new ThreadStart(RunThread));
t.Start();
}

private void RunThread()
{
Thread.Sleep(1000);
this.BeginInvoke(ThreadFinished());
}

private Delegate ThreadFinished()
{
lblStatus.Text = "After thread";
return null;
}
}
}
 
J

Jon Skeet [C# MVP]

Warren said:
Ok, here goes. I understand controls can only be accessed by the thread that
created them. I thought that a container's BeginInvoke method would force
the delegate passed to it to execute asynchronously on said thread.

Below, I create a Label and add it to my Form. I then create a new thread
running the method RunThread. RunThread sleeps for 1 second (simulating work
being done), and then uses the Form?s BeginInvoke method to call the
ThreadFinished method. ThreadFinished updates the Label to reflect the
program?s status. I believe the ThreadFinished method should be running on
the same thread that created the Form because I used the Form?s BeginInvoke
method. However, I instead get an AccessViolationException at the line that
updates the Label?s text telling me that I can only access controls on the
thread that created them. Can anyone tell me why this is happening? I?m
using C# 2.0 Beta 2.

Right - the problem is that you're *calling* ThreadFinished rather than
providing a *delegate* to ThreadFinished. In other words, you should be
doing:

this.BeginInvoke (ThreadFinished);

rather than

this.BeginInvoke (ThreadFinished());

Currently you're effectively doing:

Delegate x = ThreadFinish(); // Eek! This is changing the text in the
// worker thread!
this.BeginInvoke (x); // This does nothing as x is actually null.
 

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