简体   繁体   English

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

[英]Replacement for SynchronizationContext.Send() in Portable Class Libraries

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. 我正在编写一个可移植类库,它将被WPF,Windows Phone和可能的WinRT应用程序使用,我正在做一些有时需要回调到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. 我在UI线程中实例化这样做的类,因此我可以轻松保存SynchronizationContext并使用它来回调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. 但是,在PCL中,SynchronizationContext.Send()已过时,因为WinRT不支持它,并且SynchronizationContext.Post()(异步运行)并不总是合适的。

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. 我想我只是等到传递给Post()的委托运行,但是如果从保存的SynchronizationContext所引用的同一线程调用Post(),我的所有尝试都以死锁结束。

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

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. 我认为Microsoft正在弃用SynchronizationContext.Send是有充分理由的。 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. 他们真的希望新的Windows应用商店和WP8应用完全采用异步编程模型,并使阻塞代码成为过去。

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. 也就是说,我想您有充分的理由在PCL库中使用SynchronizationContext.Send

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. 逻辑的第一部分看起来很好,你可以通过简单地记住UI线程的Thread.CurrentThread.ManagedThreadId ,在你记忆UI线程的SynchronizationContext.Current的同一个地方来切断它的反射部分。 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. 然后在您的InvokeSynchronized实现中,您只需将它与当前线程的Thread.CurrentThread.ManagedThreadId进行比较,如果您在非UI线程上,则使用waitHandle.WaitOne()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM