I have following code:
[TestMethod]
public void StartWorkInFirstThread()
{
if (SynchronizationContext.Current == null)
SynchronizationContext.Set
Default implementation of SynchronizationContext just executes passed delegate in the calling thread (in the thread that invokes Send/Post method not the thread that captures context). If you need some particular behavior, like thread affinity for some operations, you should implement this manually. BCL contains few out-of-box implementations for simplification of UI interoperability, like WindowsFormsSynchronizationContext or DispatcherSynchronizationContext.
See http://www.codeproject.com/KB/threads/SynchronizationContext.aspx
There is the answer you need. You must override SynchronizationContext
to make it properly handling your operations.
Read starting from:
Notice that DoWork is executed on thread 11, the same thread as Run1. Not much of a SynchronizationContext into the main thread. Why? What's going on? Well... This is the part when you realize that nothing is for free in life. Threads can't just switch contexts between them, they must have an infrastructure built-in into them in order to do so. The UI thread, for example, uses a message pump, and within its SynchronizationContext, it leverages the message pump to sync into the UI thread.
Your expectation is wrong because there's no general way to "inject" a delegate into a running thread. Your "first thread" was started in the test runner, will execute one or more tests, and will then stop - there's no way to interrupt it and tell it to run CallbackInFirstThread
. The SynchronizationContext
class runs Post
-ed delegates in the thread pool because that's about the only option it has.
Derived classes like WindowsFormsSynchronizationContext
make use of the message loop in WinForms applications to pass the Post
-ed delegate to the UI thread, but there's no equivalent in a test runner.
If you want to check which SynchronizationContext
the code you're testing is using, you could create your own derived class that sets a flag you can check in your test. Here's an example:
public class TestSynchronizationContext : SynchronizationContext
{
[ThreadStatic]
private static object _CurrentPostToken;
/// <summary>
/// Gets the context's token, if the current thread is executing a delegate that
/// was posted to this context; otherwise, null.
/// </summary>
public static object CurrentPostToken
{
get
{
return _CurrentPostToken;
}
}
public object Token { get; private set; }
/// <summary>
/// Gets a WaitHandle that is set after the context executes a posted delegate.
/// </summary>
public AutoResetEvent PostHandle { get; private set; }
public TestSynchronizationContext(object token)
{
Token = token;
PostHandle = new AutoResetEvent(false);
}
public override void Post(SendOrPostCallback d, object state)
{
try
{
_CurrentPostToken = Token;
// Execute the callback on this thread, so that we can reset the context
// when it's finished.
d(state);
}
finally
{
_CurrentPostToken = null;
}
// The test method will wait on this handle so that it doesn't exit before
// the synchronization context is called.
PostHandle.Set();
}
}
In StartWorkInFirstThread
, set the context to an instance of TestSynchronizationContext
:
SynchronizationContext.SetSynchronizationContext(
new TestSynchronizationContext(new object()));
After you call BeginInvoke
, you need to wait for the Post
to happen before you exit the test, so call:
((TestSynchronizationContext)SynchronizationContext.Current).PostHandle.WaitOne(1000);
In CallbackInFirstThread
you can check what context is being used with something like:
Assert.IsNotNull(TestSynchronizationContext.CurrentPostToken);
The point is that there's no easy way to actually post back to the first thread, but you can check that the right context is being used so that, when your code runs in a real application, the callback will be running in the UI thread.