【发布时间】:2016-06-14 17:43:30
【问题描述】:
在客户端上调用生成的异步方法时,我发现 WCF 出现问题...如果我等待异步方法,然后在同一客户端上调用非异步方法,则阻塞方法永远不会返回.
这是问题的简单再现:
[ServiceContract]
public interface IService1
{
[OperationContract] void DoWork();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service1 : IService1
{
public void DoWork()
{
}
}
这里没有什么复杂的。公开单个方法的简单 WCF 服务(同步实现)。
class Program
{
static void Main(string[] args)
{
var svc = new ServiceHost(typeof(Service1));
svc.Open();
RunClient().Wait(); // This is a console app. There is no SynchronizationContext to confuse things.
svc.Close();
}
static async Task RunClient()
{
var client = new ServiceReference1.Service1Client();
client.DoWork();
Console.WriteLine("Work Done");
await client.DoWorkAsync();
Console.WriteLine("Async Work Done");
Console.WriteLine("About to block until operation timeout...");
client.DoWork();
Console.WriteLine("You'll never get here.");
}
}
请注意,这不是常见的情况,即有人阻止消息泵线程或忘记调用ConfigureAwait(false)。这是一个控制台应用,添加ConfigureAwait 对行为没有影响。
奇怪的是,帮助是做什么的:
await Task.Delay(1); // Magical healing worker thread context switch
在再次调用同步 WCF 方法之前。因此,在从异步方法调用恢复后,WCF 似乎以某种方式留下了一些线程本地上下文,然后将其清除。
知道是什么原因造成的吗? 死锁/非死锁情况的调用堆栈揭示了 WCF 客户端操作的一些差异:
良好的堆栈:
System.ServiceModel.dll!System.ServiceModel.Channels.TransportDuplexSessionChannel.Receive(System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceive(System.TimeSpan timeout, out System.ServiceModel.Channels.Message message) System.ServiceModel.dll!System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(System.ServiceModel.Channels.Message message, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.Call(string action, bool oneway, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation, object[] ins, object[] outs, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(System.Runtime.Remoting.Messaging.IMethodCallMessage methodCall, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.Invoke(System.Runtime.Remoting.Messaging.IMessage message)
mscorlib.dll!System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(ref System.Runtime.Remoting.Proxies.MessageData msgData, int type)
坏栈:
mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext)
mscorlib.dll!System.Threading.WaitHandle.WaitOne(System.TimeSpan timeout, bool exitContext)
System.ServiceModel.Internals.dll!System.Runtime.TimeoutHelper.WaitOne(System.Threading.WaitHandle waitHandle, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.Call(string action, bool oneway, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation, object[] ins, object[] outs, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(System.Runtime.Remoting.Messaging.IMethodCallMessage methodCall, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.Invoke(System.Runtime.Remoting.Messaging.IMessage message)
mscorlib.dll!System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(ref System.Runtime.Remoting.Proxies.MessageData msgData, int type)
在异步调用完成后,DuplexChannelBinder WCF 中的某处似乎假设有另一个线程从通道读取消息。
有什么想法吗?显然,我对在我的代码中添加治疗 Task.Delay 语句的想法并不感到兴奋......
干杯, 标记
【问题讨论】:
-
所以,我认为问题不是线程本地存储上下文问题。我认为这与由于 IO 完成而从 WCF 堆栈的上下文中调用的异步延续有关。当异步继续返回时(即当我们等待其他东西时),会运行更多的 WCF 框架代码来改变客户端通道的状态,并“修复”问题。
-
有什么消息吗?找到了一些真正的解决方案?我有“相同的”(?)问题......
-
对不起,如果这是愚蠢的......但是否可以保证 WCF 库创建一个具有独立执行线程的主机?在同一个线程中托管客户端和主机似乎从一开始就有问题。你能把宿主对象推到一个单独的线程中吗?
-
客户端和服务器是否在不同的进程中无关紧要,同样的问题仍然存在。上面的代码只是一个最小的复制案例。
标签: c# wcf asynchronous async-await deadlock