Phoenix
Phoenix

Reputation: 913

WCF Duplex callback method never executes when invoked from a different thread

Ok I am at my wit's end with this thing. I have a WCF duplex service. Here is how the architecture works:

  1. The client opens a connection to the endpoint and supplies a callback implementation
  2. The service takes that request and does some stuff on other threads(could be 1 second could be 2 minutes, which is the reason I am not using async operations)
  3. When the processing is done, it calls the callback of the client

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

Answers (1)

Scott Chamberlain
Scott Chamberlain

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

Related Questions