WinForms UI Thread Safety (Controls) & Events

G

Guest

I'm quite sure this is the case, but I'd like to consult to be sure:

If I have a UI which subscribes to a bunch of events which any of a number
of threads could trigger... and the UI's event handlers will update that UI's
controls, then the control updates _are_ done on the UI thread... correct?

Thanks,

Andrew
 
T

Timofey Kazakov

Hello, "Andrew"
I'm quite sure this is the case, but I'd like to consult to be sure:

If I have a UI which subscribes to a bunch of events which any of a number
of threads could trigger... and the UI's event handlers will update that UI's
controls, then the control updates _are_ done on the UI thread... correct?

All control updates must be done in UI thread. So if you have event handler that could invoked from another thread - you need to switch to UI thread through Control.Invoke method.

As another case you can create RealProxy wrapper and all method of MarshalByRefObject will be invoked on UI thread transparently:

class Program : MarshalByRefObject
{
static Program instance;

void Test()
{
Console.WriteLine(Thread.CurrentThread.Name);
}

static void Main(string[] args)
{
Thread.CurrentThread.Name = "Main Thread";

//all method's called through instance whil be done on main thread.
instance = (Program) ApartmentProxy.Marshal(new Program());

Thread thread = new Thread(new ThreadStart(ThreadStart));
thread.Start();

//this sample based on APC (asynchronous procedure call), so we need periodically to call Thread.Sleep(0) on working thread
Thread.Sleep(1000);
}

//Thread method
static void ThreadStart()
{
Thread.CurrentThread.Name = "Child Thread";

Console.WriteLine(Thread.CurrentThread.Name);
Console.WriteLine("Calling instance.Test");

// this method will be invoked on main thread
instance.Test();
}
}

//ApartmentProxy implementation
public class ApartmentProxy : RealProxy
{
delegate void ApcHandler(IntPtr param);

[DllImport("kernel32.dll")]
extern static IntPtr GetCurrentThreadId();

[DllImport("kernel32.dll")]
extern static bool QueueUserAPC(ApcHandler hadler, IntPtr thread, IntPtr param);

[DllImport("kernel32.dll")]
extern static IntPtr GetCurrentThread();

[DllImport("kernel32.dll")]
extern static IntPtr GetCurrentProcess();

[DllImport("kernel32.dll")]
extern static bool CloseHandle(IntPtr handle);

[DllImport("kernel32.dll")]
extern static bool DuplicateHandle(IntPtr sourceProcessHandle,
IntPtr sourceHandle, IntPtr targetProcessHandle,
out IntPtr targetHandle, uint desiredAccess, bool inheritHandle,
uint dwOptions);

private class ApartmentMessage
{
private ApartmentProxy _proxy;
private IMessage _source;
private IMessage _result;

private ApartmentMessage(ApartmentProxy proxy, IMessage source) {
_proxy = proxy;
_source = source;
}

private static void UserApcHandler(IntPtr param)
{
GCHandle messageHandle = (GCHandle)param;
ApartmentMessage This = (ApartmentMessage)messageHandle.Target;
try {
IMethodCallMessage mcm = This._source as IMethodCallMessage;

if (mcm != null) {
This._result = RemotingServices.ExecuteMessage(This._proxy.GetUnwrappedServer(), mcm);
}
else {
This._result = This._proxy.InitializeServerObject((IConstructionCallMessage) This._source);
}
}
finally {
This._proxy._event.Set();
}
}

public static IMessage ExecuteAppratmentMessage(ApartmentProxy proxy, IMessage msg)
{
ApartmentMessage appMessage = new ApartmentMessage(proxy, msg);

GCHandle messageHandle = GCHandle.Alloc(appMessage);
ApcHandler apcHandler = new ApcHandler(UserApcHandler);

bool bResult = QueueUserAPC(apcHandler, proxy._ownedThread, (IntPtr)messageHandle);
proxy._event.WaitOne();

GC.KeepAlive(apcHandler);
messageHandle.Free();

return appMessage._result;
}
}

internal IntPtr _ownedThread;
internal AutoResetEvent _event;

private ApartmentProxy(Type serverType)
: base (serverType)
{
IntPtr currentThread = GetCurrentThread();
IntPtr currentProcess = GetCurrentProcess();
DuplicateHandle(currentProcess, currentThread, currentProcess, out _ownedThread, 0, false, 2);

_event = new AutoResetEvent(false);
}

private ApartmentProxy(MarshalByRefObject mbr)
: this (mbr.GetType())
{
AttachServer(mbr);
}

~ApartmentProxy()
{
if (_ownedThread != IntPtr.Zero) {
CloseHandle(_ownedThread);
}
}

public override IMessage Invoke(IMessage msg)
{
IConstructionCallMessage ccm = msg as IConstructionCallMessage;
if (ccm != null) {
RealProxy.SetStubData(this, GetCurrentThreadId());
return InitializeServerObject(ccm);
}
lock (typeof(ApartmentMessage))
{
return ApartmentMessage.ExecuteAppratmentMessage(this, msg);
}
}

public static MarshalByRefObject Marshal(MarshalByRefObject obj)
{
return (MarshalByRefObject) new ApartmentProxy(obj).GetTransparentProxy();
}
}
 
J

Jon Skeet [C# MVP]

Andrew said:
I'm quite sure this is the case, but I'd like to consult to be sure:

If I have a UI which subscribes to a bunch of events which any of a number
of threads could trigger... and the UI's event handlers will update that UI's
controls, then the control updates _are_ done on the UI thread... correct?

No - events naturally occur in the thread which raises them, not the
thread which subscribes to them. You need to make sure you only access
the UI in the UI thread.

See http://www.pobox.com/~skeet/csharp/threads/winforms.shtml for more
information.
 

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