我正在编写一个将由 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/