从不同线程调用时,WCF Duplex 回调方法永远不会执行
Posted
技术标签:
【中文标题】从不同线程调用时,WCF Duplex 回调方法永远不会执行【英文标题】:WCF Duplex callback method never executes when invoked from a different thread 【发布时间】:2016-02-09 17:01:41 【问题描述】:好吧,我对这件事束手无策。我有 WCF 双工服务。以下是架构的工作原理:
-
客户端打开到端点的连接并提供回调实现
该服务接受该请求并在其他线程上执行一些操作(可能是 1 秒可能是 2 分钟,这就是我不使用异步操作的原因)
处理完成后,调用客户端的回调
问题是当服务调用该回调时,似乎什么也没发生。没有错误,什么都没有。经过进一步调查,我在服务器跟踪中发现了一个异常:
The I/O operation has been aborted because of either a thread exit or an application request
这发生在尝试执行回调之后。
客户端永远不会收到响应,或者关闭。所发生的一切是,由于初始请求是在与主线程不同的线程上发出的,因此它会永远在那里等待该线程完成。
最奇怪的是,如果我尝试在客户端调用的操作中调用回调,而不进入另一个线程,一切正常 - 成功调用回调,这让我相信我已经配置服务正确,但存在线程/死锁问题。
这是我调用服务的方式:
SubmissionServiceClient client = CreateClientInstance();
client.Open();
Guid executionId = await client.SubmitAsync(submission);
submissionCompletionSource.Task.Wait(); //waits for the callback to be called (I omitted the extra wiring code for better readability)
client.Close();
private SubmissionServiceClient CreateClientInstance()
NetHttpBinding binding = new NetHttpBinding();
binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;
EndpointAddress endpointAddress = new EndpointAddress("ws://localhost:9080/SubmissionRouter");
InstanceContext instanceContext = new InstanceContext(this);
SubmissionServiceClient submissionServiceClient = new SubmissionServiceClient(instanceContext,binding,endpointAddress);
return submissionServiceClient;
这是回调操作:
public void SubmissionProcessed(SubmissionResultDto result)
submissionCompletionSource.TrySetResult(result);
这是客户端调用的服务操作:
public Guid Submit(SubmissionDto submission, ISubmissionCallback callback)
ExecutionDto execution = new ExecutionDto()
Id = Guid.NewGuid(),
Submission = submission
;
RequestExecution(execution); //Queues the execution of the operation
submissions.Add(execution.Id, callback);
return execution.Id;
这是服务调用客户端回调的地方(此方法在与发出初始请求的线程不同的线程上执行):
ISubmissionCallback callback = submissions[submissionResult.ExecutionId];
callback.SubmissionProcessed(submissionResult);
服务行为:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]
正如您在提交时看到的那样,服务将回调存储在字典中,并与一个 id 配对,稍后它会使用它来检索回调并调用它。我相信它失败的原因是因为我试图在不同的线程上这样做。
编辑: 我在服务中添加了一个 Ping 操作,它调用另一个线程,该线程挂起 3 秒,然后在客户端调用 Pong 函数。
public void Ping()
var callback = OperationContext.Current.GetCallbackChannel<ISubmissionCallback>();
Task.Run(() =>
System.Threading.Thread.Sleep(3000);
callback.Pong();
);
客户: 课堂节目 静态无效主要(字符串 [] 参数) NetHttpBinding 绑定 = new NetHttpBinding(); binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always; EndpointAddress endpointAddress = new EndpointAddress("ws://localhost:9080/SubmissionRouter"); InstanceContext instanceContext = new InstanceContext(new Callback()); SubmissionServiceClient submitServiceClient = new SubmissionServiceClient(instanceContext, binding, endpointAddress);
submissionServiceClient.Ping();
Console.Read();
public void SubmissionProcessed(SubmissionResultDto result)
throw new NotImplementedException();
class Callback : ISubmissionServiceCallback
public void Pong()
Console.WriteLine("Pong!");
public void SubmissionProcessed(SubmissionResultDto result)
这实际上是成功的。我设法在客户端收到了我的答案。我现在完全迷失了。
【问题讨论】:
作为一个测试,您能否尝试在您的线程上回调一个非常基本的方法,例如不采用任何参数的方法,例如Ping
,看看会发生什么?建立连接后尝试限制通道上的任何其他流量/操作,以便简化重新创建。
您是否维护对服务器端连接的引用? (不仅仅是回调引用)
你用submissionCompletionSource.Task.Wait();
阻塞了UI线程吗?另外,当你之前有await
时,你为什么要打电话给Wait
,你不是更愿意await submissionCompletionSource.Task
吗?
@wal 我更新了帖子。我不确定这是否是你想让我尝试的,但它确实给出了一些有趣的结果。我也不确定你的第二个问题是什么意思。如果您问我是否保留对最初从 (SubmissionServiceClient) 发出请求的通道的引用,那么答案是肯定的,因为在调用回调之前该方法永远不会返回。 submitCompletionSource.Task.Wait();
@ScottChamberlain 是的,该行旨在阻止当前线程,直到我得到响应。之前的等待实际上并不重要,因为提交操作几乎立即返回(它所做的只是将请求排队等待执行并生成一个 Guid)。它很可能是一个同步操作。 Task.Wait 和 await Task 有区别吗,如果有,能给我解释一下吗?
【参考方案1】:
如果您使用 submissionCompletionSource.Task.Wait();
阻塞 UI 线程,这会导致您的死锁。 WCF 回调默认发生在 UI 线程上,您可以使用 CallbackBehaviorAttribute
更改行为
[CallbackBehaviorAttribute(UseSynchronizationContext=false)]
class Callback : ISubmissionServiceCallback
public void Pong()
Console.WriteLine("Pong!");
public void SubmissionProcessed(SubmissionResultDto result)
或者不阻塞 UI 线程。
SubmissionServiceClient client = CreateClientInstance();
client.Open();
Guid executionId = await client.SubmitAsync(submission);
await submissionCompletionSource.Task; //awaits for the callback to be called without blocking the UI.
client.Close();
【讨论】:
我已经尝试过 UseSynchronizationContext=false 但这没有帮助。我会尽快尝试您的第二个建议。 你先生真棒!建议 2 奏效了。现在,如果我能理解为什么。 @Phoenix 发生这种情况是因为您同步等待异步操作,here is a in-depth blog about the issue以上是关于从不同线程调用时,WCF Duplex 回调方法永远不会执行的主要内容,如果未能解决你的问题,请参考以下文章
为啥 WCF 服务能够处理来自不同进程的调用而不是来自线程的调用
当 WCF 方法抛出异常时,使用空响应调用 jQuery 成功回调