GUI Thread - Is cross-thread operation really bad?


K

kndg

Hi all,

When I first learn threading, I had always been advised that doing a
cross-thread operation on a GUI thread is not safe and should be
avoided. But it is not until recently that it makes me wonder why that
is bad. Quick googling directs me to STA and COM which I have no idea
about. But, if it really that bad, why it is not enforced (during
runtime)? For example, I was unable to make below code to throw the
exception.

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace MyNamespace
{
public class MyForm : Form
{
private Label label1;
private Button button1;

public MyForm()
{
InitializeComponent();
}

void InitializeComponent()
{
label1 = new Label();
label1.Location = new Point(10, 10);
label1.Size = new Size(200, 20);

button1 = new Button();
button1.Location = new Point(10, 30);
button1.Text = "Start";
button1.Click += button1_Click;

Controls.Add(label1);
Controls.Add(button1);
}

void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
new Thread(SimulateLongRunningWork).Start();
}

void SimulateLongRunningWork()
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100); // simulate long running task

label1.Text = "Progress: " + i + "%"; // <- cross-thread operation
}

label1.Text += " Completed!"; // <- cross-thread operation
button1.Text = "Completed"; // <- cross-thread operation
}

[STAThread]
public static void Main(string[] args)
{
Application.Run(new MyForm());
}
}
}

Yes, it will throw exception when running inside IDE/debugger, but if I
run it alone, it will just run - no exception thrown. This is contrast
to WPF which always throws exception (with/without debugger).

Does anyone have any idea why?
 
Ad

Advertisements

J

Jeroen van Langen

?Hi,

The problem with cross threading on the GUI thread is, that your controls
are updated/repainted on events raised by windows. (like WM_Paint,
WM_MouseMove etc messages)
The painting is done with code (WndProc) you don't see or even want to
handle.
You also want to avoid writing on a control that is disposed/destroyed.
Reading and writing should be synchronized.
This could be solved by overriding the WndProc and build a lock on it, be I
won't recommend it!!

So? how to update controls on a thread?
You can invoke a delegate/method on the GUI thread with Windows.Forms.

void SimulateLongRunningWork()
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(10); // simulate long running task

SetLabelText(label1, "Progress: " + i + "%");
}

SetLabelText(label1, GetLabelText(label1) + " Completed!"); //
<- cross-thread operation
SetButtonText(button1, "Completed"); // <- cross-thread
operation
}

private void SetButtonText(Button button, string message)
{
if (button.InvokeRequired)
button.Invoke(new Action<Button, string>(SetButtonText),
label, message);
else
{
button.Text = message;
}
}

private void SetLabelText(Label label, string message)
{
if (label.InvokeRequired)
label.Invoke(new Action<Label, string>(SetLabelText), label,
message);
else
{
label.Text = message;
}
}

private string GetLabelText(Label label)
{
if (label.InvokeRequired)
return Convert.ToString(label.Invoke(new Func<Label,
string>(GetLabelText), label));
else
{
return label.Text;
}
}

On the other hand. WPF validates if you're updating a control on the
Dispatcher thread. This way the WPF control prevents unpredictable behavior.
It will throw an exception so your are forced to update it on the gui
dispatcher thread.

void SimulateLongRunningWork()
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(10); // simulate long running task

SetLabelText(Label1, "Progress: " + i + "%");
}

SetLabelText(Label1, GetLabelText(Label1) + " Completed!"); // <-
cross-thread operation
SetButtonText(ButtonTextblock, "Completed"); // <- cross-thread
operation
}

private void SetButtonText(TextBlock buttonTextblock, string message)
{
buttonTextblock.Dispatcher.Invoke(new Action<TextBlock,
string>(delegate
{
buttonTextblock.Text = message;
}), buttonTextblock, message);
}

private void SetLabelText(Label label, string message)
{
label.Dispatcher.Invoke(new Action<Label, string>(delegate
{
label.Content = message;
}), label, message);
}

private string GetLabelText(Label label)
{
return Convert.ToString(label.Dispatcher.Invoke(new Func<Label,
object>(delegate
{
return label.Content;
}), label));
}

So? The only reason why WPF raises a exception is to prevent unpredictable
behavior. You don't want a application that could go wrong sometimes.

Here's is some information why you shouldn't update controls on multiple
threads:
http://msdn.microsoft.com/en-us/library/ms173179.aspx


Regards,
Jeroen



"kndg" schreef in bericht
Hi all,

When I first learn threading, I had always been advised that doing a
cross-thread operation on a GUI thread is not safe and should be
avoided. But it is not until recently that it makes me wonder why that
is bad. Quick googling directs me to STA and COM which I have no idea
about. But, if it really that bad, why it is not enforced (during
runtime)? For example, I was unable to make below code to throw the
exception.

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace MyNamespace
{
public class MyForm : Form
{
private Label label1;
private Button button1;

public MyForm()
{
InitializeComponent();
}

void InitializeComponent()
{
label1 = new Label();
label1.Location = new Point(10, 10);
label1.Size = new Size(200, 20);

button1 = new Button();
button1.Location = new Point(10, 30);
button1.Text = "Start";
button1.Click += button1_Click;

Controls.Add(label1);
Controls.Add(button1);
}

void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
new Thread(SimulateLongRunningWork).Start();
}

void SimulateLongRunningWork()
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100); // simulate long running task

label1.Text = "Progress: " + i + "%"; // <- cross-thread operation
}

label1.Text += " Completed!"; // <- cross-thread operation
button1.Text = "Completed"; // <- cross-thread operation
}

[STAThread]
public static void Main(string[] args)
{
Application.Run(new MyForm());
}
}
}

Yes, it will throw exception when running inside IDE/debugger, but if I
run it alone, it will just run - no exception thrown. This is contrast
to WPF which always throws exception (with/without debugger).

Does anyone have any idea why?
 
K

kndg

The problem with cross threading on the GUI thread is, that your
controls are updated/repainted on events raised by windows. (like
WM_Paint, WM_MouseMove etc messages)
The painting is done with code (WndProc) you don't see or even want to
handle.

I'm not sure if I understand it correctly... Are you saying that when
updating the control from different thread, the windows didn't get the
message? But from my observation, that is not the case.
You also want to avoid writing on a control that is disposed/destroyed.
Reading and writing should be synchronized.

I think it is same with other multi-threading scenario. What I don't
understand is why they (Microsoft) emphasis on avoiding doing a
cross-thread operation on GUI thread (even we, as user, can design it to
be safe to manipulate). Worst case, we could even put a lock on a
control (sorry, just saying - I haven't try it yet).
This could be solved by overriding the WndProc and build a lock on it,
be I won't recommend it!!

So? how to update controls on a thread?
[...]

Thanks, but it require different syncronization strategy for WinForm
(Invoke/BeginInvoke) and WPF (Dispatcher.Invoke/BeginInvoke). It makes
the code less reusable. By the way, when I learning the TPL, there is a
hidden gem that works on both WinForm & WPF. It is called
SynchronizationContext. Sample code below (just for sharing)

void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
new
Thread(SimulateLongRunningWork).Start(SynchronizationContext.Current);
}

void SimulateLongRunningWork(object syncContext)
{
var uiContext = (SynchronizationContext)syncContext;

for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100); // simulate long running task

uiContext.Send((n) =>
{
label1.Text = "Progress: " + n + "%";
}, i);
}

uiContext.Send((n) =>
{
label1.Text += " Completed!";
button1.Text = "Completed";
}, null);
}

You can refactor out the GUI things and it will works on both WinForm & WPF.
 
K

kndg

[...]
Yes, it will throw exception when running inside IDE/debugger, but if I
run it alone, it will just run - no exception thrown. This is contrast
to WPF which always throws exception (with/without debugger).

Does anyone have any idea why?

You are really asking two questions:

• Why doesn't it throw an exception? And,
• Is cross-thread usage bad?

The answer the first is simply because Microsoft decided for whatever
reason to limit the exception to debugging scenarios. I can understand
that decision, though I don't agree with it. And based on your
description of the WPF behavior (I didn't verify), it seems that they
themselves reconsidered (though of course we're almost certainly talking
about different people for each feature, it seems very likely that the
WPF folks made their decision knowing that the Forms folks went the
other way, and understanding the implications of deciding one way or the
other).

Note that you can change the behavior if desired by setting the static
Control.CheckForIllegalCrossThreadCalls property to "true". Then the
exception will occur with or without the debugger attached.

The answer to the second question is a bit more "hand-wavy", but comes
down to the issue that the Win32 API itself creates thread affinity for
windows and their message queues, and for whatever reason the .NET
designers decided to make that explicit in the .NET APIs.

The big issue with cross-thread access is, of course, that done without
synchronization, you can corrupt your data structures. Even if you are
lucky and that doesn't happen, it can cause strange, hard-to-debug
behaviors.

For any Forms controls that are based on, and delegate 100% of their
behavior to, pre-existing Win32 controls, then in theory it would be
safe, because interaction with the controls relies on window messages,
which are always marshaled by Windows to the correct thread (the one
that owns the control).

But a) this implicit marshaling can cause hard-to-debug deadlock
problems, and b) makes an assumption that is going to false nearly 100%
of the time (i.e. that 100% of the behavior is delegated to the Win32
control via window messages).

Even for Forms controls that are in fact based on Win32 controls, there
are good reasons to add a layer of managed code to do some caching and
add enhanced behaviors. And for Forms controls that are written mostly
from scratch (they all inherit Control, and so all wind up having a
window handle at some point….NET Forms doesn't have the concept of
lightweight controls as some Java GUI APIs do), this is even more of a
problem.

Note that one major issue is that there are a number of members of the
Control class that when accessed will implicitly cause the window handle
for that Control instance to be created (creation of the window handle
is generally deferred, not done when the object itself is instantiated).
If you accidentally cause the window handle to be created on the wrong
thread, then your control just isn't going to work correctly, because
that's the thread where the messages for the control will be sent, and
you probably won't have a message pump running on that thread, so the
control will never get its messages.

I'm just speculating, but it seems to me that given all the various ways
the threading stuff can go wrong, and given all the different
implementations and their various reliance or lack thereof with respect
to the current thread, it made sense to just unify the whole thing and
require the use of the object to always occur on the thread that owns
it. That's a lot easier, to implement, to test, and to educate about,
than trying to provide a more elaborate solution.

Now, as for why WPF went that direction I'm not entirely sure. WPF
primarily uses DirectX as its underlying unmanaged API which, while not
thread-safe per se, at least is more straight-forward about its lack of
thread-safety. But, WPF is still a GUI and still relies on windows and
their owner threads to deal with the user, and so perhaps there were
enough issues similar to those the Forms API has to deal with that it
made more sense to follow the same kind of paradigm.

To be sure, while the cross-thread invocation requirement can be a
hassle sometimes, it is well-enforced by the API, and when you comply
with it, it does make all the other cross-thread issues that might occur
when dealing with GUI objects completely moot. And if you follow the
same cross-thread invocation paradigm for your own code, then threading
issues become moot in your own code as well. It's a fairly heavy-weight
way to synchronize code in different threads, but it's very effective. :)

Pete

Thanks Pete (as always). I will take a while to chew on.
Regarding the Control.CheckForIllegalCrossThreadCalls property, is there
a similar in WPF so that I would like to make the cross-thread calls to
pass (means no exception thrown)? I would like to see the cross-thread
behaviour in WPF.
 
A

Arne Vajhøj

When I first learn threading, I had always been advised that doing a
cross-thread operation on a GUI thread is not safe and should be
avoided. But it is not until recently that it makes me wonder why that
is bad. Quick googling directs me to STA and COM which I have no idea
about. But, if it really that bad, why it is not enforced (during
runtime)? For example, I was unable to make below code to throw the
exception.

You should avoid cross thread calls because the documentation
state that many parts of win forms is not thread safe.

Doing cross thread calls may cause unpredictable results
in current .NET version or may work fine in current version
but cause unpredictable results in a future .NET version or
may work for ever.

It is not good to write code that just may work, so don't
do it.

If you want to enforce it then just add:

Control.CheckForIllegalCrossThreadCalls = true;

to the form constructor, then you will get an exception.

The fault value is:

Debugger.IsAttached

Or at least that is what is claimed at:

http://msdn.microsoft.com/en-us/lib....control.checkforillegalcrossthreadcalls.aspx

Arne
 
K

kndg

You should avoid cross thread calls because the documentation
state that many parts of win forms is not thread safe.

Yes, I know, but isn't it the same with the most class libraries in the
..Net framework? Uhh..my meaning is, since the controls is not thread
safe, then we have to take extra care when dealing with it. Since we
already have many synchronization techniques at our disposal (lock,
Monitor, EventWaitHandle...) then why don't we just use that. But
instead, we do the hassle of forcing them to do their work on original
thread through Invoke.

For example, (I modified my original code from label to textbox)

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace MyNamespace
{
public class MyForm : Form
{
private TextBox textBox1;
private Button button1;

public MyForm()
{
InitializeComponent();
}

void InitializeComponent()
{
textBox1 = new TextBox();
textBox1.Location = new Point(10, 10);
textBox1.Size = new Size(250, 200);
textBox1.Multiline = true;

button1 = new Button();
button1.Location = new Point(10, 230);
button1.Text = "Start";
button1.Click += button1_Click;

Controls.Add(textBox1);
Controls.Add(button1);
}

void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
new Thread(SimulateLongRunningWork).Start();
new Thread(SimulateLongRunningWork).Start();
new Thread(SimulateLongRunningWork).Start();
new Thread(SimulateLongRunningWork).Start();
}

void SimulateLongRunningWork()
{
for (int i = 1; i <= 100; i++)
{
textBox1.Text += " " + i; // <- cross-thread operation
}
}

[STAThread]
public static void Main(string[] args)
{
Application.Run(new MyForm());
}
}
}

I spawn four thread and as expected, the textbox doesn't display the
correct result. But instead the hassle of Invoke,

void SimulateLongRunningWork()
{
for (int i = 1; i <= 100; i++)
{
Invoke((MethodInvoker)delegate
{
textBox1.Text += " " + i;
});
}
}

why don't we just put a lock to it?

private object locker = new object();

void SimulateLongRunningWork()
{
for (int i = 1; i <= 100; i++)
{
lock(locker)
{
textBox1.Text += " " + i;
}
}
}

But in reality, Microsoft insist that the GUI should be updated on their
original thread and they even enforce it in WPF (so you can't use lock
and other synchronization techniques). So, there must be "something"
that the lock can't provide and it is that "something" that bothers me.
 
Ad

Advertisements

K

kndg

[...]

... And if you follow the
same cross-thread invocation paradigm for your own code, then threading
issues become moot in your own code as well...

That's... sparks my interest! How do I do that? Err.. I mean (GUI thingy
aside..) how do I code, so that the code will run in original thread?
For example,

public class DummyClass
{
public string DummyString { get; set; }

public void StartThread()
{
new Thread(DummyMethod).Start();
}

private void DummyMethod()
{
???((MethodInvoker)delegate // <-- ???: the equivalance of
Control.Invoke
{
DummyString = "dummy";
}
}
}
 
K

kndg

I'm not sure if I understand it correctly... Are you saying that when
updating the control from different thread, the windows didn't get the
message? But from my observation, that is not the case.

Define "updating the control from [a] different thread". Data members in
your own code can be updated from a different thread. You can even call
the Invalidate() method from a different thread (though the
documentation does not include that method in the "magic five" that are
allowed from another thread).

I mean as per my sample code. In other word, manipulating the control
property from different thread than the thread that own it.

label1.Text = "Progress: " + i + "%";
But the _visual_ update itself (i.e. the redraw) always happens in the
thread that owns the control, and class members defined by .NET in
Control sub-classes may or may not work correctly when used from the
wrong thread.


I don't know what "doing a cross-thread operation on GUI thread" means.
The whole point of the guidance is to make sure that you only access
Control members on the GUI thread. You wouldn't do a cross-thread
operation there, because you're already there; it's not cross-thread.

Yes, but it is not enforce in WinForm. You can still access it from
different thread (ie. cross thread). Or, is my understanding on
"cross-thread" wrong?
Do you simply mean that a "cross-thread operation" (i.e. an operation
begun from a thread other than the one that owns the control) should be
invoked onto the GUI thread?


A "lock" is a useful form of synchronization in many situations. But it
doesn't apply when dealing with the thread affinity issues that are
"baked into" Windows. Certain things simply _must_ happen on the thread
that owns the control; simply ensuring no concurrent access to the same
data member isn't sufficient.

Okay, but that "certain things" that really bother me. Could you give me
some example that, if it is not done on their own thread can spell disaster?
This could be solved by overriding the WndProc and build a lock on it,
be I won't recommend it!!

So? how to update controls on a thread?
[...]

Thanks, but it require different syncronization strategy for WinForm
(Invoke/BeginInvoke) and WPF (Dispatcher.Invoke/BeginInvoke). It makes
the code less reusable.

If you need your code to be portable between Forms and WPF, don't use
their API-specific mechanisms. Instead, use the SynchronizationContext
class for both. In either API, SynchronizationContext.Current will
return an instance of SynchronizationContext that will marshal
invocations of delegates passed to the Post() and Send() methods back to
the thread on which the Current property value was retrieved.

That doesn't mean that there's something wrong with the APIs in Forms
and WPF. Fact is, in most cases you don't want code outside your GUI
worrying about cross-thread operations. BackgroundWorker (for example)
is specifically designed for the job, so it generalizes by using
SynchronizationContext. But most asynchronous code should not, because
it's code that is not GUI-specific at all (i.e. it would work just as
well in other code that has no knowledge of a GUI).

Instead, most code should simply run on whatever thread it's supposed to
run on. And then any GUI code that may respond to that code (e.g. by
subscribing to an event) can then handle the cross-thread operation as
appropriate. And since the GUI code is generally specific to a
particular GUI API (Forms or WPF or whatever), it can deal with the
cross-thread operation in an API-specific way (such as in Jeroen's
example(*)).

Pete

(*) (though, as I've mentioned before here, I disagree that the
InvokeRequired property serves any useful purpose; one should just
always call Invoke() if it's possible to be needed. See
http://msmvps.com/blogs/duniho/arch...chnique-for-using-control-invoke-is-lame.aspx
for more details)
 
J

J van Langen

?> But in reality, Microsoft insist that the GUI should be updated on their
original thread and they even enforce it in WPF (so you can't use lock and
other synchronization techniques). So, there must be "something" that the
lock can't provide and it is that "something" that bothers me.

I know what u mean, but as I was saying. Many control updates are done thru
the windows message loop. So if you want to lock that, you have to lock the
whole message loop.

http://en.wikipedia.org/wiki/Message_loop_in_Microsoft_Windows

Something like this:

while(GetMessage(Msg, null, 0, 0) > 0)
{
TranslateMessage(&Msg);
Lock(MessageLoopLock)
{
DispatchMessage(&Msg);
}
}

But I don't advise you to do this!

Also the windows message loop is implemented by the framework.
My opinion is, if you like to have things done on a separate thread, you
should synchronize it.

In summary, well matured and entrenched STA (Single Threaded Apartment)
design constraints permeate throughout Window based controls. This means
that only the thread that created the control may manipulate the control.
Deviation from this constraint can and will lead to very strange behaving
applications.
 
K

kndg

[...]

The issue with the messages is if the Control instance is owned by the
wrong thread. If it was created on the same thread where your message
pump is running (i.e. your Form application's main thread), it will get
any window message sent to it, as long as the message pump is allowed
run (i.e. you haven't blocked that thread with some other code that sits
and waits rather than returning immediately). Windows will see to that,
even if the message is sent from a different thread.

The issues raised here are:

• The Control is _not_ created on that main thread. Then the object is
owned by a different thread, almost certainly one without a message pump
running. Then it won't get any messages.

• The Control is created on the main thread, but you attempt to use it
from a different thread. As has been pointed out already, the failure
case here is much more subtle: it often _will_ work, but the problem is
you never know when it won't.

Ahh... okay. Now I understand the difference.
So, I experimenting with your first point. I try creating a Label on
different thread and try to add it to the main form but at runtime it
throws ArgumentException.

void CreateAdditionalLabel()
{
var label2 = new Label();
label2.Text = "created from different thread";
Controls.Add(label2); // <- ArgumentException
}

System.ArgumentException: Controls created on one thread cannot be
parented to a control on a different thread.

Hmmm... okay. I modified the code to below,

void CreateAdditionalLabel()
{
var label2 = new Label();
label2.Text = "created from different thread";
Invoke((MethodInvoker)(() => Controls.Add(label2)));
}

And the code run just fine. I thought maybe Controls.Add() is doing the
trick, so I try to see what would happen if I create a form instead.

void CreateAdditionalForm()
{
var form = new MyForm();
form.ShowDialog();
}

And the form shown without problem. Hmm... it seems that the window
still get the message even if it is created on different thread. Is my
observation correct?
[...]
Okay, but that "certain things" that really bother me. Could you give me
some example that, if it is not done on their own thread can spell
disaster?

Not without putting myself through a refresher course on the topic, and
then spending a lot of time writing all the details down for you. It's
not really in the scope of this newsgroup anyway; if you really want to
understand those types of issues, you need to become an expert in Win32
programming.

The short answer is: Windows was originally single-threaded only. As it
evolved into a multi-threaded/preemptive-multi-tasking OS, a lot of
effort was put into allow old single-threaded code to work, without
having to touch that actual code, but for it to do so in a
multi-threaded environment. This necessarily introduced requirements
that certain parts of the code _had_ to execute only on one specific
thread.

In some cases, the failure is no different from what you'd see if you
failed to synchronize properly between threads, except that you don't
have that option. The only synchronization available that will ensure
correct behavior is to follow the Windows thread-affinity model.

In other cases, there would be more specific ties to the thread in
question, such as use of thread-local-storage. Issues like this would
manifest differently, such as by the code seeing one value at certain
times and a completely different value at other times.

Okay, I will put some thought on this.
 
K

kndg

[...]

... And if you follow the
same cross-thread invocation paradigm for your own code, then threading
issues become moot in your own code as well...

That's... sparks my interest! How do I do that? Err.. I mean (GUI thingy
aside..) how do I code, so that the code will run in original thread?

Do you mean with or without the context of an actual GUI thread?

If in the context of a GUI thread, it's simple: just get the current
SynchronizationContext for the thread where you want everything to
execute, and then always use Post() or Send() on that Sync…Context, just
as you'd do with GUI-related code.

If you're looking to accomplish it for non-GUI thread, you can implement
your own Sync…Context. Google this newsgroup for past discussions; I
recall a previous discussion in which someone wanted to do that. The
basic implementation is simply a consumer of a delegate queue, though of
course it can be somewhat more complicated if you want all the same
features that the GUI APIs or Sync…Context provide (such as synchronous
invocation, returning values from such, etc.).

Hmm... is it the only way is through SynchronizationContext, or there
are some other ways? By the way, I found a very nice article on this
subject and below are the links for anyone interested. I haven't finish
reading it yet but it seems great!

http://www.codeproject.com/KB/threads/SynchronizationContext.aspx
http://www.codeproject.com/KB/threads/SynchronizationContext2.aspx
http://www.codeproject.com/KB/threads/SynchronizationContext3.aspx
 
Ad

Advertisements

J

J van Langen

Hi,

Correct me if I'm wrong,
As far as I know the invocation cross threads is done thru the windows
message queue. Invoke will become a SendMessage and BeginInvoke a
PostMessage.

So if you want to have methods invoked on your threads, you need to build a
queue that will be handled by the thread. When you want to execute a method
on that thread, create a MethodInfo (nothing more than a delegate and
parameters) and add it to the queue.
The thread will check the queue and invoke the Method. This will be a fire
and forget way. If you want to check a result, you should block the calling
thread until the Invoked thread is done. (you could make this with a
AutoResetEvent/ManualResetEvent added to the Method. Also a result can be
added.

I've made an example (minimal validation checking) but you could do
something like this:

public partial class Form1 : Form
{
InvocationThread _invocationThread;

public Form1()
{
InitializeComponent();
Thread.CurrentThread.Name = "GUI Thread";
_invocationThread = new InvocationThread();
}

private void Form1_Load(object sender, EventArgs e)
{
Console.WriteLine("Current thread before call :
"+Thread.CurrentThread.Name);
int result = Convert.ToInt32(_invocationThread.Invoke(new
Func<int, int, int>(AddNumbers), 10, 10));

Console.WriteLine("Current thread after call : " +
Thread.CurrentThread.Name);
Console.WriteLine(result.ToString());
}

private int AddNumbers(int a, int b)
{
Console.WriteLine("Current thread within the call : " +
Thread.CurrentThread.Name);
return a + b;
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
_invocationThread.Dispose();
}
}

// ---------------------------------------------------------------------

public class InvocationInfo
{
public Delegate Delegate { get; private set; }
public object[] Args { get; private set; }
public object Result { get; set; }
public ManualResetEvent Done { get; private set; }

public InvocationInfo(Delegate method, object[] args)
{
Delegate = method;
Args = args;
Done = new ManualResetEvent(false);
}
}

// -----------------------------------------------------------------------

public class InvocationThread : IDisposable
{
private Thread _thread;
private List<InvocationInfo> _methodQueue = new
List<InvocationInfo>();

public object Invoke(Delegate method, params object[] args)
{
InvocationInfo info = new InvocationInfo(method, args);
lock (_methodQueue)
_methodQueue.Add(info);

info.Done.WaitOne();

return info.Result;
}

private void ThreadMethod()
{
Thread.CurrentThread.Name = "Invocation Thread";
try
{
while (true)
{
InvocationInfo[] invocations;

lock (_methodQueue)
{
invocations = _methodQueue.ToArray();
_methodQueue.Clear();
}

if (invocations.Length > 0)
{
foreach (InvocationInfo invocation in invocations)
{
invocation.Result =
invocation.Delegate.Method.Invoke(invocation.Delegate.Target,
invocation.Args);
invocation.Done.Set();
}
}
else
Thread.Sleep(10);
}
}
catch (ThreadInterruptedException)
{
// Thread is interrupted.
}
}

public InvocationThread()
{
_thread = new Thread(new ThreadStart(ThreadMethod));
_thread.Start();
}


public void Dispose()
{
_thread.Interrupt();
_thread.Join();
}
}
 
J

J van Langen

> You should use the Monitor class rather than WaitHandles. The Monitor
class is native to .NET, while the WaitHandle sub-classes use unmanaged
Windows API objects.
The explicit use of Monitor to accomplish the same thing as a
WaitHandle-based solution is a bit wordier, but it's easy enough to wrap
that up in a Monitor-based class to help the code be more concise.

So you indicate that the ManualResetEvent is too heavy weight for this
situation. I normally use the Monitor class only for locking data/resources.

Jeroen
 
J

Jeroen van Langen

?>> The problem with cross threading on the GUI thread is, that your
I'm not sure if I understand it correctly... Are you saying that when
updating the control from different thread, the windows didn't get the
message? But from my observation, that is not the case.

The window will get the message, but you don't have any control on
repainting or the repaint message accessing the controls resources. So you
cannot determine if your thread writes to data and the repaint message
handler (gui thread) reading the same resources.

Jeroen
 
A

Arne Vajhøj

Yes, I know, but isn't it the same with the most class libraries in the
.Net framework?

Not quite.

There are two differences:
- the GUI framework expects the event thread to do things
- the GUI framework is very deep with lots of stuff inside
Uhh..my meaning is, since the controls is not thread
safe, then we have to take extra care when dealing with it. Since we
already have many synchronization techniques at our disposal (lock,
Monitor, EventWaitHandle...) then why don't we just use that. But
instead, we do the hassle of forcing them to do their work on original
thread through Invoke.

For example, (I modified my original code from label to textbox)
why don't we just put a lock to it?

private object locker = new object();

void SimulateLongRunningWork()
{
for (int i = 1; i <= 100; i++)
{
lock(locker)
{
textBox1.Text += " " + i;
}
}
}

But in reality, Microsoft insist that the GUI should be updated on their
original thread and they even enforce it in WPF (so you can't use lock
and other synchronization techniques). So, there must be "something"
that the lock can't provide and it is that "something" that bothers me.

That construct does not make the update happen in the event thread. This
is GUI specific.

It does not prevent any internal concurrency issues. You only
lock on your locker object, so it is only your code that synchronizes
access to textBox1. Various other code inside win forms does not have
its access synchronized by this. This is not GUI specific, but is
probably a bigger problem in GUI than in most other stuff.

Arne
 
K

kndg

[...]
void CreateAdditionalForm()
{
var form = new MyForm();
form.ShowDialog();
}

And the form shown without problem. Hmm... it seems that the window
still get the message even if it is created on different thread. Is my
observation correct?

You are seeing the expected behavior in your last example. But this is
because the ShowDialog() method has a built-in message loop. If you were
to call Show() instead, you would find that the Form does not redraw
correctly after it's initially shown (and possibly not even
initially…this will vary depending on a number of factors).

The reason I don't use Show() is because I can't stop the thread from
dying (the form just instantly shown and then disappear). I modified the
code as below just to block the thread from dying,

void CreateAdditionalForm()
{
var form = new MyForm();
form.Show();

var isClosed = false;
form.FormClosed += (o, e) => isClosed = true;

while(!isClosed)
{
Thread.Sleep(1000);
form.Refresh();
//Application.DoEvents();
}
}

I had put a button on a form and when I call Refresh(), the button does
displayed but the form seems hang and unable to process other messages.
When I replace the form.Refresh() statement to Application.DoEvents(),
the form seems able to process other messages and somewhat responsive.
However, I don't understand why. Does it mean that although the form is
create on different thread, as long it has a message loop, the message
ultimately will get processed? I try to read the source code for the
Application class but it is beyond my capability to understand (it is
somewhat complex and full of native calls). Anyway below is the full
source code.

using System;
using System.Threading;
using System.Windows.Forms;

namespace MyNamespace
{
public class MyForm : Form
{
private Button button1;

public MyForm()
{
button1 = new Button();
button1.Text = "Start";
button1.Click += button1_Click;

Controls.Add(button1);
}

void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
//new Thread(CreateAdditionalLabel).Start();
new Thread(CreateAdditionalForm).Start();
}

void CreateAdditionalLabel()
{
var label2 = new Label();
label2.Text = "created from different thread";
Invoke((MethodInvoker)(() => Controls.Add(label2)));
}

void CreateAdditionalForm()
{
var form = new MyForm();
form.Show();

bool isClosed = false;
form.FormClosed += (o, e) => isClosed = true;

while(!isClosed)
{
Thread.Sleep(1000);
form.Refresh();
//Application.DoEvents();
}
}

[STAThread]
public static void Main(string[] args)
{
Application.Run(new MyForm());
}
}
}
 
Ad

Advertisements

K

kndg

Hi,

Correct me if I'm wrong,
As far as I know the invocation cross threads is done thru the windows
message queue. Invoke will become a SendMessage and BeginInvoke a
PostMessage.

So if you want to have methods invoked on your threads, you need to
build a queue that will be handled by the thread. When you want to
execute a method on that thread, create a MethodInfo (nothing more than
a delegate and parameters) and add it to the queue.
The thread will check the queue and invoke the Method. This will be a
fire and forget way. If you want to check a result, you should block the
calling thread until the Invoked thread is done. (you could make this
with a AutoResetEvent/ManualResetEvent added to the Method. Also a
result can be added.

I've made an example (minimal validation checking) but you could do
something like this:

[...]

Thanks! I will study your source code and learning the idea.
 
K

kndg

[...]

Note that the new async features proposed for C# 5.0 will rely on a
SynchronizationContext to handle continuation code executing in the same
context the preceding code was executing.
http://msdn.microsoft.com/en-us/vstudio/gg316360

I had a time playing with Async CTP before and my impression is, totally
awesome! I think, it should have been in C# 4 and along with the TPL, it
is a killer feature.
 
J

Jeroen van Langen

A way to block the current thread is replace the .Show() of the form with
ShowDialog() that will make the form modal.
Show dialog will handle the messages for the dialog.

Jeroen

"kndg" schreef in bericht
[...]
void CreateAdditionalForm()
{
var form = new MyForm();
form.ShowDialog();
}

And the form shown without problem. Hmm... it seems that the window
still get the message even if it is created on different thread. Is my
observation correct?

You are seeing the expected behavior in your last example. But this is
because the ShowDialog() method has a built-in message loop. If you were
to call Show() instead, you would find that the Form does not redraw
correctly after it's initially shown (and possibly not even
initially…this will vary depending on a number of factors).

The reason I don't use Show() is because I can't stop the thread from
dying (the form just instantly shown and then disappear). I modified the
code as below just to block the thread from dying,

void CreateAdditionalForm()
{
var form = new MyForm();
form.Show();

var isClosed = false;
form.FormClosed += (o, e) => isClosed = true;

while(!isClosed)
{
Thread.Sleep(1000);
form.Refresh();
//Application.DoEvents();
}
}

I had put a button on a form and when I call Refresh(), the button does
displayed but the form seems hang and unable to process other messages.
When I replace the form.Refresh() statement to Application.DoEvents(),
the form seems able to process other messages and somewhat responsive.
However, I don't understand why. Does it mean that although the form is
create on different thread, as long it has a message loop, the message
ultimately will get processed? I try to read the source code for the
Application class but it is beyond my capability to understand (it is
somewhat complex and full of native calls). Anyway below is the full
source code.

using System;
using System.Threading;
using System.Windows.Forms;

namespace MyNamespace
{
public class MyForm : Form
{
private Button button1;

public MyForm()
{
button1 = new Button();
button1.Text = "Start";
button1.Click += button1_Click;

Controls.Add(button1);
}

void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
//new Thread(CreateAdditionalLabel).Start();
new Thread(CreateAdditionalForm).Start();
}

void CreateAdditionalLabel()
{
var label2 = new Label();
label2.Text = "created from different thread";
Invoke((MethodInvoker)(() => Controls.Add(label2)));
}

void CreateAdditionalForm()
{
var form = new MyForm();
form.Show();

bool isClosed = false;
form.FormClosed += (o, e) => isClosed = true;

while(!isClosed)
{
Thread.Sleep(1000);
form.Refresh();
//Application.DoEvents();
}
}

[STAThread]
public static void Main(string[] args)
{
Application.Run(new MyForm());
}
}
}
 
Ad

Advertisements

K

kndg

Thanks to all who replied (Peter, Jeroen and Arne).
My current job limits me to dig deeper on this issue.
Sure it fun but I'm satisfied with the answer given.
Thanks for your time and the knowledge shared.

Regards.
 

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