Reputation: 913
Ok I am at my wit's end with this thing. I have a WCF duplex service. Here is how the architecture works:
The problem is when the service calls that callback, seemingly nothing happens. No error, no anything. Upon further investigation I found an exception in the server trace:
The I/O operation has been aborted because of either a thread exit or an application request
This happens right after attempting to execute the callback.
The client never receives a response, or closes. All that happens is, since the initial request is made on a thread different from the main one, it just waits there forever for that thread to complete.
The most weird thing is, that if I try to call the callback in the operation that the client invokes, without going into another thread, everything works fine - the callback is successfully called, which leads me to believe that I have configured the service correctly, but have a threading/deadlocking problem.
Here is how I am calling the service:
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;
}
This is the callback operation:
public void SubmissionProcessed(SubmissionResultDto result)
{
submissionCompletionSource.TrySetResult(result);
}
This is the service operation that the client calls:
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;
}
And this is where the service calls the callback of the client(this method is executed on a different thread from the one that the initial request was made on):
ISubmissionCallback callback = submissions[submissionResult.ExecutionId];
callback.SubmissionProcessed(submissionResult);
Service behaviour:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]
As you can see upon submission, the service stores the callback in a dictionary, paired with an id, which it later uses to retrieve the callback and invoke it. I believe the reason it is failing is because I am trying to do that on a different thread.
EDIT: I added a Ping operation to the service, that calls another thread, which hangs for 3 seconds and then calls a Pong function on the client.
public void Ping()
{
var callback = OperationContext.Current.GetCallbackChannel<ISubmissionCallback>();
Task.Run(() =>
{
System.Threading.Thread.Sleep(3000);
callback.Pong();
});
}
client: class Program { static void Main(string[] args) { NetHttpBinding binding = new NetHttpBinding(); binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always; EndpointAddress endpointAddress = new EndpointAddress("ws://localhost:9080/SubmissionRouter"); InstanceContext instanceContext = new InstanceContext(new Callback()); SubmissionServiceClient submissionServiceClient = 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)
{
}
}
}
This actually worked successfully. I managed to receive my answer on the client side. I am now officially completely lost.
Upvotes: 4
Views: 1090
Reputation: 127543
If you are blocking the UI thread with submissionCompletionSource.Task.Wait();
that is causing your deadlock. WCF Callbacks happen on the UI thread by default, you can change the behavior by using CallbackBehaviorAttribute
[CallbackBehaviorAttribute(UseSynchronizationContext=false)]
class Callback : ISubmissionServiceCallback
{
public void Pong()
{
Console.WriteLine("Pong!");
}
public void SubmissionProcessed(SubmissionResultDto result)
{
}
}
or by not blocking the UI thread.
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();
Upvotes: 3