Events - thread safe?

D

Dan Tallent

I am trying to learn how to create an application that uses worker threads
to prevent the UI from freezing up.
My question is: Is the following code considered "thread safe"?
If not, what am I doing wrong?

Thanks
Dan

--------------------------------------------------------
I have a form with this code:
--------------------------------------------------------
private void button1_Click(object sender, System.EventArgs e)

{

WorkerClass wc = new WorkerClass();

wc.ProcessComplete+= new WorkerClass.ProcessCompleteHandler(WorkerDone);

wc.Process();

Console.WriteLine("Waiting for worker thread");

}

public void WorkerDone(long current)

{

Console.WriteLine("main: Worker thread complete" + current.ToString());

this.textBox1.Text = current.ToString();

}



--------------------------------------------------------

A also have a seperate class with this code:

--------------------------------------------------------

using System;

using System.Threading;

namespace TestWorkerThread

{

/// <summary>

/// Summary description for WorkerClass.

/// </summary>

public class WorkerClass

{

long Result;

public delegate void ProcessCompleteHandler(long current);

public event ProcessCompleteHandler ProcessComplete;



public WorkerClass()

{

//

// TODO: Add constructor logic here

//


}

public void Process()

{


// Create a new thread and call a function on that new thread.

// This method should return without waiting


ThreadStart myThreadDelegate = new ThreadStart(ProcessData);

Thread myThread = new Thread(myThreadDelegate);

myThread.Start();



}

private void ProcessData()

{

Result = 0;


for (int x = 0; x<10; x++)

{

Result = Result + x;

Thread.Sleep(1000);

}


// Fire the event signal. Any subscribers will then know the processing is
done.

OnProcessComplete(Result);

}

protected void OnProcessComplete(long current)

{

ProcessComplete(current);

}

}

}



----------------------------------------------------------------
 
N

Nicholas Paldino [.NET/C# MVP]

Dan,

Events are fired on the thread that they are called from. Because of
this, your ProcessCompleteHandler which sets the text in the textbox is not
correct.

What you need to do is have your call to set the Text property of the
TextBox on the UI thread. You can do this like this:

// Define the delegate.
delegate void WorkerDoneCallback(long current);

Then, when you set up the callback for the ProcessCompleteHandler, I
would do the following:

wc.ProcessComplete += delegate(long current)
{
// Call the WorkerDone method on the UI thread.
this.Invoke(new WorkerClass.ProcessCompleteHandler(WorkerDone), new
object[1]{current});
}

Basically, you have set up an anonymous delegate which will call your
WorkerDone method through the Invoke method on the Control.

Hope this helps.
 
B

Brian Gideon

Dan,

No, it is not thread-safe. Windows controls can only be accessed from
the thread they were created on. Typically, that's the main UI thread.
What you need to do is marshal the execution of a delegate onto that
thread using Control.Invoke.

public void WorkerDone(long current)
{
if (this.InvokeRequired)
{
Delegate method = new ProcessCompleteHandler(this.WorkerDone);
object[] args = new object[] { current };
this.Invoke(method, args);
}
else
{
Console.WriteLine("main: Worker thread complete" +
current.ToString());
this.textBox1.Text = current.ToString();
}
}


Read the following article for more information.

<http://www.yoda.arachsys.com/csharp/threads/winforms.shtml>

Brian
 
D

Dan Tallent

I could see how this could work, but what if I want the function in the
worker thread to be independent of a specific form? I thought using
subscribing to events in this manner would allow this. I will take a look
at the link you supplied. Thanks

Dan

Brian Gideon said:
Dan,

No, it is not thread-safe. Windows controls can only be accessed from
the thread they were created on. Typically, that's the main UI thread.
What you need to do is marshal the execution of a delegate onto that
thread using Control.Invoke.

public void WorkerDone(long current)
{
if (this.InvokeRequired)
{
Delegate method = new ProcessCompleteHandler(this.WorkerDone);
object[] args = new object[] { current };
this.Invoke(method, args);
}
else
{
Console.WriteLine("main: Worker thread complete" +
current.ToString());
this.textBox1.Text = current.ToString();
}
}


Read the following article for more information.

<http://www.yoda.arachsys.com/csharp/threads/winforms.shtml>

Brian

Dan said:
I am trying to learn how to create an application that uses worker
threads
to prevent the UI from freezing up.
My question is: Is the following code considered "thread safe"?
If not, what am I doing wrong?

Thanks
Dan

--------------------------------------------------------
I have a form with this code:
--------------------------------------------------------
private void button1_Click(object sender, System.EventArgs e)

{

WorkerClass wc = new WorkerClass();

wc.ProcessComplete+= new WorkerClass.ProcessCompleteHandler(WorkerDone);

wc.Process();

Console.WriteLine("Waiting for worker thread");

}

public void WorkerDone(long current)

{

Console.WriteLine("main: Worker thread complete" + current.ToString());

this.textBox1.Text = current.ToString();

}



--------------------------------------------------------

A also have a seperate class with this code:

--------------------------------------------------------

using System;

using System.Threading;

namespace TestWorkerThread

{

/// <summary>

/// Summary description for WorkerClass.

/// </summary>

public class WorkerClass

{

long Result;

public delegate void ProcessCompleteHandler(long current);

public event ProcessCompleteHandler ProcessComplete;



public WorkerClass()

{

//

// TODO: Add constructor logic here

//


}

public void Process()

{


// Create a new thread and call a function on that new thread.

// This method should return without waiting


ThreadStart myThreadDelegate = new ThreadStart(ProcessData);

Thread myThread = new Thread(myThreadDelegate);

myThread.Start();



}

private void ProcessData()

{

Result = 0;


for (int x = 0; x<10; x++)

{

Result = Result + x;

Thread.Sleep(1000);

}


// Fire the event signal. Any subscribers will then know the processing
is
done.

OnProcessComplete(Result);

}

protected void OnProcessComplete(long current)

{

ProcessComplete(current);

}

}

}



----------------------------------------------------------------
 
D

Dan Tallent

Ok, I think I get it.

Because the calling function is on a different thread, I need to use the
Invoke method for the WorkerDone method.
And writting the WorkerDone method in this way prevents the WorkerClass from
"needing to know" anything about the form.
The InvokeRequired property is an "allowed" exception to the rule, which
allows me to call it either directly or marshalling it if required.

If I am wrong with this description, please let me know.

I really appreciate your help.

Thanks
Dan


Dan Tallent said:
I could see how this could work, but what if I want the function in the
worker thread to be independent of a specific form? I thought using
subscribing to events in this manner would allow this. I will take a
look at the link you supplied. Thanks

Dan

Brian Gideon said:
Dan,

No, it is not thread-safe. Windows controls can only be accessed from
the thread they were created on. Typically, that's the main UI thread.
What you need to do is marshal the execution of a delegate onto that
thread using Control.Invoke.

public void WorkerDone(long current)
{
if (this.InvokeRequired)
{
Delegate method = new ProcessCompleteHandler(this.WorkerDone);
object[] args = new object[] { current };
this.Invoke(method, args);
}
else
{
Console.WriteLine("main: Worker thread complete" +
current.ToString());
this.textBox1.Text = current.ToString();
}
}


Read the following article for more information.

<http://www.yoda.arachsys.com/csharp/threads/winforms.shtml>

Brian

Dan said:
I am trying to learn how to create an application that uses worker
threads
to prevent the UI from freezing up.
My question is: Is the following code considered "thread safe"?
If not, what am I doing wrong?

Thanks
Dan

--------------------------------------------------------
I have a form with this code:
--------------------------------------------------------
private void button1_Click(object sender, System.EventArgs e)

{

WorkerClass wc = new WorkerClass();

wc.ProcessComplete+= new WorkerClass.ProcessCompleteHandler(WorkerDone);

wc.Process();

Console.WriteLine("Waiting for worker thread");

}

public void WorkerDone(long current)

{

Console.WriteLine("main: Worker thread complete" + current.ToString());

this.textBox1.Text = current.ToString();

}



--------------------------------------------------------

A also have a seperate class with this code:

--------------------------------------------------------

using System;

using System.Threading;

namespace TestWorkerThread

{

/// <summary>

/// Summary description for WorkerClass.

/// </summary>

public class WorkerClass

{

long Result;

public delegate void ProcessCompleteHandler(long current);

public event ProcessCompleteHandler ProcessComplete;



public WorkerClass()

{

//

// TODO: Add constructor logic here

//


}

public void Process()

{


// Create a new thread and call a function on that new thread.

// This method should return without waiting


ThreadStart myThreadDelegate = new ThreadStart(ProcessData);

Thread myThread = new Thread(myThreadDelegate);

myThread.Start();



}

private void ProcessData()

{

Result = 0;


for (int x = 0; x<10; x++)

{

Result = Result + x;

Thread.Sleep(1000);

}


// Fire the event signal. Any subscribers will then know the processing
is
done.

OnProcessComplete(Result);

}

protected void OnProcessComplete(long current)

{

ProcessComplete(current);

}

}

}



----------------------------------------------------------------
 
B

Brian Gideon

Dan said:
Ok, I think I get it.

Because the calling function is on a different thread, I need to use the
Invoke method for the WorkerDone method.
And writting the WorkerDone method in this way prevents the WorkerClass from
"needing to know" anything about the form.

Yeah, that's right. The WorkerClass doesn't know anything about who is
subscribed to its events. But, the form knows how the WorkerClass
behaves. Specifically, it knows that the WorkerClass' events are not
guarenteed to be executed on the UI thread so the form needs to ensure
that.

There is an alternate design pattern that I've used before. Take a
look at the System.Timers.Timer class. You'll notice that it has a
SynchronizingObject property that accepts an ISynchronizeInvoke object.
Forms and Controls implement ISynchronizeInvoke. Actually, that's the
interface that contains the InvokeRequired, Invoke, etc. methods. When
that property is set to a form or control it will automatically marshal
events and callbacks onto the thread hosting that object. You could do
something similar with your WorkerClass.
The InvokeRequired property is an "allowed" exception to the rule, which
allows me to call it either directly or marshalling it if required.

Yes, InvokeRequired, Invoke, and BeginInvoke are all thread-safe. They
are among the few exceptions.
 

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