我不确定为什么会如此。但我确定如果使用异步调用,则不会阻止。
这里的场景是我有2个WCF方法调用,第一个将触发对客户端的一些回调调用(使用CallbackContract
)。第二个只是一个普通的WCF方法调用(即使是一个根本没有代码的空方法)。
方法内容并不重要,这里只是某种伪代码:
public void FirstMethod(){
//some logic here...
//here I use some Callback method to client side
clientCallbackInterface.SomeMethod();//commenting this out won't
//cause any blocking.
}
public void SecondMethod(){
//this is even empty
}
//call the 2 methods synchronously in a sequence
client.FirstMethod();
client.SecondMethod();
不调用SecondMethod
,它运行得很好。如果使用异步调用,它也运行得很好。或者如果我注释掉调用(使用客户端回调接口),它也会运行得很好。
在抛出异常TimeoutException
时,它显示方法SecondMethod
实际上已完成并处于响应客户端的阶段。
ServiceBehavior有InstanceContextMode
PerSession
和ConcurrencyMode
Multiple
。
我希望此处有人对此有所了解并理解这个问题背后的原因。
更新:
ConcurrencyMode
设置为Single
来尝试新事物,而且运行得很好。所以我想了解更多如何使ConcurrencyMode
Multiple
{/ 1}}正常运行?更新:
我真的很困惑这里有什么问题,事实上有一些旧的代码甚至没有使用CallbackBehavior
,它只能与ConcurrencyMode
的{{1}}一起使用。虽然我的代码需要Multiple
,但在第二次执行2个方法时失败了。这是我可以发布的最小代码,我已经尝试过了,方法内容并不重要,它可能只是空的:
CallbackBehavior
上面的//the service interface
[ServiceContract(CallbackContract = typeof(IMyClient), SessionMode = SessionMode.Allowed)]
public interface IMyService
{
bool MyMethod();
}
//the client callback interface
public interface IMyClient
{
[OperationContract(IsOneWay = true)]
void OnSomething(SomeEventArgs e);
}
//the service class
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public class MyService : IMyService
{
static Dictionary<ClientInfo, IMyClient> clients;
static Dictionary<ClientInfo, IMyClient> Clients
{
get
{
if (clients == null)
clients = new Dictionary<ClientInfo, IMyClient>();
return clients;
}
}
static void raiseEvents(Action<IMyClient> raiser, params Guid[] toClients)
{
if (raiser == null)
throw new ArgumentNullException("raiser cannot be null.");
System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(o => {
lock (clients)
{
//ClientInfo is just some class holding some info about
//the client such as its ClientGuid
Func<KeyValuePair<ClientInfo, IMyClient>, bool> filter = c => toClients.Length == 0 || toClients.Any(e => e == c.Key.ClientGuid);
foreach (var client in Clients.Where(filter).Select(e => e.Value))
{
raiser(client);
}
}
}));
}
public bool MyMethod(){
//do nothing before trying to trigger some callback to the client
raiseEvents(e => e.OnSomething(new SomeEventArgs()));
return true;
}
}
方法实际上是我遵循的旧代码(正如我所提到的那样工作得很好),就像之前它看起来更简单(并且也不起作用):
raiseEvents
旧代码和我编写的代码之间的一个可能的区别在于配置文件,但我不确定哪个可能导致此问题。事实上,我已尽可能多地克隆配置(关于static void raiseEvents(Action<IMyClient> raiser, params Guid[] toClients)
{
if (raiser == null)
throw new ArgumentNullException("raiser cannot be null.");
Func<KeyValuePair<ClientInfo, IPosClient>, bool> filter = c => toClients.Length == 0 || toClients.Any(e => e == c.Key.ClientGuid);
foreach (var client in Clients.Where(filter).Select(e => e.Value))
{
Task.Run(() => raiser(client));
}
}
)。
如最初所述,这里涉及两种方法。但是这次我只有一个方法,如代码中所示。它第一次调用OK时,下次调用它将冻结UI(就像有一些死锁一样)。当您拥有客户端代理类(由“添加服务引用”向导自动生成)时,调用它很简单:
<behaviors>
事实上,我可以使用//this is put in some Execute method of some Command (in WPF)
myServiceClient.MyMethod();
的{{1}}版本解决此问题,或者只是将该调用放在一个帖子中,但旧代码不需要这样做而且我可以使用async
。我真的很好奇为什么它第一次起作用,但下次一直保持冻结(直到MyMethod
被抛出)。
答案 0 :(得分:1)
默认情况下,WCF将在当前的SynchronizationContext上执行回调。这意味着当你从例如WPF或WinForms应用程序的UI线程调用WCF服务时 - 回调也将在UI线程上执行。但是这个线程已被您的服务调用阻止,因此您对服务和服务回调客户端的调用将会死锁。首先 - 不要从UI线程调用远程服务,无论如何从用户体验的角度来看都是坏的(你的界面会在等待调用结果时冻结)。但是,如果您仍然这样做,至少通过使用CallbackBehavior属性告诉WCF不使用当前的同步上下文进行回调:
[CallbackBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant, UseSynchronizationContext=false)]
class Callback : IClientCallback
{
}