在可移植类库中替换SynchronizationContext.Send()

时间:2014-03-16 19:09:18

标签: c# multithreading portable-class-library

我正在编写一个可移植类库,它将由WPF,Windows Phone和可能的WinRT应用程序使用,而且我正在做一些有时需要回调到UI的后台线程的工作。我在UI线程中实例化了这样做的类,因此我可以轻松保存SynchronizationContext并使用它来回调UI。

但是,在PCL中,SynchronizationContext.Send()已过时,因为WinRT不支持它,并且SynchronizationContext.Post()(异步运行)并不总是合适。

我认为我只是等到传递给Post()的委托运行,但是如果从保存的SynchronizationContext所引用的同一个线程调用Post(),我的所有尝试都会以死锁结束。

现在我已经设法通过检查它是否是同一个线程并且只是简单地调用我的委托来解决这个问题,但是这些检查是非常难看的,涉及反映出私有字段的值。 API,所以我认为有人可以帮助我找到更合适的方式。

如果你想看到一些血腥,这是我现在的代码:

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

}

谢谢!

1 个答案:

答案 0 :(得分:3)

我认为SynchronizationContext.Send被微软推荐是有充分理由的。他们真的希望新的Windows应用商店和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;
}

那就是说,我想你有充分的理由在你的PCL库中使用SynchronizationContext.Send

你的逻辑的第一部分看起来不错,你可以通过简单地记住UI线程的Thread.CurrentThread.ManagedThreadId,在你记住SynchronizationContext.Current的{​​{1}}的同一个地方来切断它的反射部分。 UI线程。然后在InvokeSynchronized的实现中,您只需将其与当前线程的Thread.CurrentThread.ManagedThreadId进行比较,如果您在非UI线程上,则使用waitHandle.WaitOne()