c# - 替换可移植类库中的 SynchronizationContext.Send()

标签 c# multithreading portable-class-library

我正在编写一个将由 WPF、Windows Phone 和可能的 WinRT 应用程序使用的可移植类库,并且我正在对偶尔需要回调 UI 的后台线程做一些工作。我在 UI 线程中实例化执行此操作的类,因此我可以轻松保存 SynchronizationContext 并使用它回调 UI。

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

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

现在我已经通过检查它是否是同一个线程并简单地调用我的委托(delegate)来解决这个问题,但是检查非常丑陋,涉及反射(reflect)出 API 的私有(private)字段的值,所以我认为有人可以帮助我找到更合适的方法。

如果您想看一些血腥内容,这是我当前的代码:

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

}

谢谢!

最佳答案

我认为 SynchronizationContext.Send 被 Microsoft 弃用是有充分理由的。他们真的希望新的 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 来切断它的反射部分,在你记住的同一个地方UI 线程的 SynchronizationContext.Current。然后在您的 InvokeSynchronized 实现中,您只需将它与当前线程的 Thread.CurrentThread.ManagedThreadId 进行比较,然后使用 waitHandle.WaitOne()如果您在非 UI 线程上。

关于c# - 替换可移植类库中的 SynchronizationContext.Send(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22441499/

相关文章:

c# - 从计时器线程更新 WPF UI

c# - 在 TCP 连接上接收大量数据时出现内存不足异常

c# - 等待任务完成而不阻塞 UI 线程

c++ - io_context或std::cout在多线程UDP服务器中的奇怪行为

c# - 无法在可移植类库中返回异步等待

c# - 使用同一个程序集多次调用 Assembly.Load(AssemblyName) 有什么缺点?

perl - Perl 中的非阻塞 I/O 操作是否仅限于一个线程?好的设计?

java - 在 Java 中使用锁的最佳实践是什么(面试)?

xamarin - 如何在Xamarin和Asp.net core之间共享类库?

将时间从 UTC 转换为指定的时区时出现 .NET PCL 异常