Метод обратного вызова WCF никогда не выполняется при вызове из другого потока

Хорошо, я в конце остроумие с этой вещью. У меня есть дуплексный сервис WCF. Вот как работает архитектура:

  1. Клиент открывает соединение с конечной точкой и предоставляет реализацию обратного вызова
  2. Служба принимает этот запрос и выполняет некоторые действия в других потоках (это может быть 1 секунда или 2 минуты, поэтому я не использую асинхронные операции)
  3. Когда обработка завершена, она вызывает обратный вызов клиента

Проблема в том, что когда служба вызывает этот обратный вызов, похоже, ничего не происходит. Нет ошибок, нет ничего. После дальнейшего расследования я обнаружил исключение в трассировке сервера:

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)]

Как вы можете видеть после отправки, сервис сохраняет обратный вызов в словаре в паре с идентификатором, который он позже использует для получения обратного вызова и его вызова. Я считаю, что причина его неудачи в том, что я пытаюсь сделать это в другом потоке.

РЕДАКТИРОВАТЬ: я добавил операцию Ping к службе, которая вызывает другой поток, который висит на 3 секунды, а затем вызывает функцию Pong на клиенте.

public void Ping()
        {
            var callback = OperationContext.Current.GetCallbackChannel<ISubmissionCallback>();
            Task.Run(() =>
            {
                System.Threading.Thread.Sleep(3000);
                callback.Pong();
            });
        }

клиент: класс 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)
        {

        }
    }
}

Это на самом деле сработало успешно. Мне удалось получить мой ответ на стороне клиента. Я теперь официально полностью потерян.

1 ответ

Решение

Если вы блокируете поток пользовательского интерфейса с submissionCompletionSource.Task.Wait(); это вызывает твой тупик. По умолчанию обратные вызовы WCF происходят в потоке пользовательского интерфейса. Вы можете изменить поведение, используя CallbackBehaviorAttribute

[CallbackBehaviorAttribute(UseSynchronizationContext=false)]
class Callback : ISubmissionServiceCallback
{
    public void Pong()
    {
        Console.WriteLine("Pong!");
    }

    public void SubmissionProcessed(SubmissionResultDto result)
    {

    }
}

или не блокируя поток пользовательского интерфейса.

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();
Другие вопросы по тегам