Метод обратного вызова WCF никогда не выполняется при вызове из другого потока
Хорошо, я в конце остроумие с этой вещью. У меня есть дуплексный сервис 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)]
Как вы можете видеть после отправки, сервис сохраняет обратный вызов в словаре в паре с идентификатором, который он позже использует для получения обратного вызова и его вызова. Я считаю, что причина его неудачи в том, что я пытаюсь сделать это в другом потоке.
РЕДАКТИРОВАТЬ: я добавил операцию 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();