【发布时间】: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 有区别吗,如果有,能给我解释一下吗?
标签: c# .net multithreading wcf duplex