Replacement for SynchronizationContext.Send () in portable class libraries

I am writing a portable class library that will be used by WPF, Windows Phone, and possibly WinRT applications, and I am doing some work on background threads that sometimes need to be called back to the user interface. I am instantiating classes that do this in the user interface thread, so I can easily save the SynchronizationContext and use it to return to the user interface.

However, in PCL, SynchronizationContext.Send () is deprecated since it is not supported by WinRT and SynchronizationContext.Post () (which runs asynchronously) is not always appropriate.

I decided that I would just wait until the delegate passed to Post () was running, but all my attempts ended in deadlock if Post () was called from the same thread referenced by the saved SynchronizationContext.

Now I managed to fix this by checking if it is the same thread and just calling my delegate if there is one, but the checks are incredibly ugly, including reflecting the values โ€‹โ€‹of the private API fields, so I thought someone could help me find more the right way.

Here is my current code if you want to see a little:

/// <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 );

}

Thank!

+4
source share
1 answer

, SynchronizationContext.Send Microsoft . , Windows Store WP8 .

, -, :

void SendDataToUI()
{
    _synchronizationContext.Send(_callback, data);
}

:

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;
}

, , SynchronizationContext.Send PCL.

, , Thread.CurrentThread.ManagedThreadId , , SynchronizationContext.Current . InvokeSynchronized Thread.CurrentThread.ManagedThreadId waitHandle.WaitOne(), , UI.

+3

Source: https://habr.com/ru/post/1532039/


All Articles