I'm writing a Portable Class Library that is going to be used by WPF, Windows Phone and possibly WinRT apps and I'm doing some work on background threads that occasionally need to call back to the UI. I instantiate the classes doing this in the UI thread, so I can easily save the SynchronizationContext and use it to call back to the UI.
However, in PCL, SynchronizationContext.Send() is obsolete, because it's not supported by WinRT and SynchronizationContext.Post() (which runs asynchronously) is not always appropriate.
I figured I'd just wait until the delegate passed to Post() is run, but all my attempts ended with a deadlock if Post() was invoked from the same thread the saved SynchronizationContext referred to.
Now I've managed to fix this by checking if it's the same thread and just simply calling my delegate if it is, but the checks are incredibly ugly involving reflecting out the value of private fields of the API, so I thought someone could help me find a more proper way.
Here is my current code if you'd like to see some gore:
/// <summary>
/// Invokes the passed callback on this SynchronizationContext and waits for its execution. Can be used even if
/// SynchronizationContext.Send is not available. Throws the exceptions thrown in the delegate.
/// </summary>
/// <param name="context">the context to run the method</param>
/// <param name="d">the method to run</param>
/// <param name="state">the parameter of the method to run</param>
public static void InvokeSynchronized( this SynchronizationContext context, SendOrPostCallback d, object state )
{
if ( !context.Match( SynchronizationContext.Current ) )
{
ManualResetEvent waitHandle = new ManualResetEvent( false );
Exception error = null;
// replicate SynchronizationContext.Send with .Post as Send is obsolete in the Portable Class Library
context.Post( ( o ) =>
{
try
{
d( o );
}
catch ( Exception exc )
{
error = exc;
}
finally
{
waitHandle.Set();
}
},
state );
waitHandle.WaitOne();
if ( error != null )
{
throw error;
}
}
else
{
d( state );
}
}
/// <summary>
/// Checks if the two SynchronizationContexts refer to the same thread
/// </summary>
/// <param name="sc1"></param>
/// <param name="sc2"></param>
/// <returns></returns>
public static bool Match(this SynchronizationContext sc1, SynchronizationContext sc2)
{
if ( sc2 == null )
{
return false;
}
else if ( sc1 == sc2 || sc1.Equals(sc2) )
{
return true;
}
// check if the two contexts run on the same thread
// proper equality comparison is generally not supported, so some hacking is required
return sc1.FindManagedThreadId() == sc2.FindManagedThreadId();
}
/// <summary>
/// Finds the ManagedThreadId of the thread associated with the passed SynchronizationContext
/// </summary>
/// <param name="sc"></param>
/// <returns></returns>
public static int FindManagedThreadId(this SynchronizationContext sc)
{
// here be dragons
try
{
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
switch ( sc.GetType().FullName )
{
case "System.Windows.Threading.DispatcherSynchronizationContext":
// sc._dispatcher.Thread.ManagedThreadId
var _dispatcher_field = sc.GetType().GetField( "_dispatcher", bindFlags );
var _dispatcher_value = _dispatcher_field.GetValue( sc );
var Thread_property = _dispatcher_value.GetType().GetProperty( "Thread", bindFlags );
var Thread_value = Thread_property.GetValue( _dispatcher_value, null ) as Thread;
return Thread_value.ManagedThreadId;
}
}
catch ( Exception e )
{
throw new InvalidOperationException( "ManagedThreadId could not be obtained for SynchronizationContext of type " + sc.GetType().FullName, e );
}
throw new InvalidOperationException( "ManagedThreadId not found for SynchronizationContext of type " + sc.GetType().FullName );
}
Thanks!
I think that SynchronizationContext.Send
is being deprecated by Microsoft for a good reason. They really want the new Windows Store and WP8 apps to fully embrace asynchronous programming model and make the blocking code a thing of the past.
So, something that was:
void SendDataToUI()
{
_synchronizationContext.Send(_callback, data);
}
Should now become:
async Task SendDataToUIAsync()
{
var tcs = new TaskCompletionSource<object>();
_synchronizationContext.Post(a =>
{
try
{
_callback(a);
tcs.SetResult(Type.Missing);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, data);
await tcs.Task;
}
That said, I suppose you have your own good reasons to use SynchronizationContext.Send
in your PCL library.
The first part of your logic looks good, and you could cut off the reflection part of it by simply memorizing the Thread.CurrentThread.ManagedThreadId
of the UI thread, at the same place where you memorize the SynchronizationContext.Current
of the UI thread. Then in your implementation of InvokeSynchronized
you just compare it to the Thread.CurrentThread.ManagedThreadId
of the current thread, and use waitHandle.WaitOne()
if your are on non-UI thread.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With