Buscando WCF Duplex "TwoWay" Suscribirse+Ejemplo de devolución de llamada
callback timeoutexception (4)
Renovando la recompensa OTRA VEZ porque realmente necesito saber cómo hacer que esto funcione, o una respuesta definitiva sobre por qué no lo hará.
He añadido una explicación alternativa del problema aquí.
Tener un infierno de tiempo para obtener un cliente-servidor WCF de dos vías (IsOneWay = falso) para trabajar en .Net 3 / 3.5.
Después de que el cliente se inscriba con éxito en el servicio, el Anuncio periódico del servicio () vuelve a llamar a los clientes inscritos. Ahora es cuando el cliente o el servidor se cuelgan hasta que transcurre el SendTimeout del servidor, ajustado a 2 segundos. Entonces el lado del servidor tiene una excepción de tiempo de espera de la siguiente manera. Solo entonces el código de usuario del cliente RECIBE EL MÉTODO LLAMADO e intenta devolver un valor. Para entonces, el zócalo del cliente se cancela y el material WCF falla.
Me parece que algo en el cliente está bloqueando su cola WCF local desde el procesamiento hasta que el socket se agote, pero no lo suficientemente pronto para cancelar la llamada del método local. Pero si se cree la excepción a continuación, el servidor está intentando enviar una operación a http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous (¡inapropiado!) Y se está agotando. Tal vez ese URI sea solo el "Nombre" del cliente conectado remotamente, ya que WCF sabe que debe referirse a él para los fines del mensaje de error y parece que significa que no se puede cargar un URI. No puedo saber si el servidor falla primero o si el cliente falla primero.
He intentado agregar el rastreo de WCF pero no obtengo mucha más información.
El código de muestra similar está aquí , pero debe haber sido demasiado para digerir. He experimentado con variaciones de ese código.
TimeoutException ''This request operation sent to http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous did not receive a reply within the configured timeout (00:00:00). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.''
Server stack trace:
at System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
En primer lugar, obtenga una copia de los servicios de programación de WCF , si aún no tiene uno.
Si el cliente es WinForm o WPF, debe usar [CallbackBehavior(UseSynchronizationContext = false)]
ya que, de lo contrario, el cliente no procesará el mensaje entrante hasta que el subproceso de la interfaz de usuario ingrese al bucle de mensajes.
En primer lugar, un canal "dúplex" en WCF no es realmente dúplex! Un mensaje de
- Cliente a servidor
- Puede bloquear un mensaje que el servidor está esperando del cliente.
- (O de otra forma)
Como los mensajes solo se envían en orden en un solo canal WCF. Un canal WCF dúplex NO le da dos colas de mensajes entrantes. Los resultados que se obtienen de una llamada "TwoWay" son los mismos que los de "call" que este nivel de la pila WCF. Una vez que te acerques a esto, muchos de los problemas se vuelven más claros de entender.
Si el cliente es WinForm o WPF, es posible que deba usar [CallbackBehavior(UseSynchronizationContext = false)]
ya que, de lo contrario, el cliente no procesará el mensaje entrante hasta que el subproceso de la interfaz de usuario ingrese al bucle de mensajes.
Algunas reglas que encontré para ayudar a evitar los puntos muertos. (¡Mira mis preguntas de WCF para ver el dolor que tuve!)
El servidor nunca debe llamar a un cliente en la misma conexión que una llamada del mismo cliente está en proceso.
Y / o
El cliente nunca debe devolver la llamada al servidor en la misma conexión que se usa para las "devoluciones de llamada" al procesar una devolución de llamada.
La próxima vez creo que usaré dos contratos (y, por lo tanto, conexiones TCP) uno para la devolución de llamada y otro para todas las solicitudes de cliente-> servidor. O usar mi propio sistema de encuestas, ya que esto me causó mucho dolor.
Lo siento, no tengo tiempo hoy para escribir un ejemplo. De todos modos, la mayoría de los ejemplos funcionan para lo que el ejemplo está tratando de hacer, pero se descomponen en la vida real por algún motivo que ver con su aplicación.
El mejor sitio web que conozco para ver ejemplos de WCF es el sitio web de Juval Lowy .
También puede encontrar útiles las preguntas que hice sobre WCF en , ya que estaba teniendo el mismo tipo de problemas que usted.
También pasar uno o dos días leyendo todas las preguntas y respuestas de WCF en le dará una buena idea de los problemas que deben evitarse.
Lo siento, olvidé totalmente el ejemplo (: - $).
Aquí está mi código para el servidor:
ISpotifyServer.cs
[ServiceContract(CallbackContract = typeof(ISpotifyCallback))]
public interface ISpotifyService
{
[OperationContract(IsOneWay = true)]
void Login(string username, string password);
}
ISpotifyCallback.cs
[ServiceContract]
public interface ISpotifyCallback
{
[OperationContract(IsOneWay = true)]
void OnLoginComplete();
[OperationContract(IsOneWay = true)]
void OnLoginError();
}
Program.cs
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(SpotifyService)))
{
host.Open();
Console.WriteLine("Service running.");
Console.WriteLine("Endpoints:");
foreach (ServiceEndpoint se in host.Description.Endpoints)
Console.WriteLine(se.Address.ToString());
Console.ReadLine();
host.Close();
}
}
}
AppData.xml
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MetadataEnabledBehavior">
<serviceMetadata />
<serviceDebug includeExceptionDetailInFaults="True"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="MetadataEnabledBehavior" name="SpotiServer.SpotifyService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9821" />
</baseAddresses>
</host>
<clear />
<endpoint address="spotiserver" binding="netTcpBinding"
name="TcpEndpoint" contract="SpotiServer.ISpotifyService"
listenUriMode="Explicit">
<identity>
<dns value="localhost"/>
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
Y para el cliente:
Program.cs
class Program
{
static void Main(string[] args)
{
InstanceContext context = new InstanceContext(new CallbackHandler());
String username;
String password;
Console.Write("Username: ");
username = Console.ReadLine();
Console.WriteLine("Password: ");
password = ReadPassword();
SpotiService.SpotifyServiceClient client = new SpotiService.SpotifyServiceClient(context);
client.Login(username, password);
Console.ReadLine();
}
private static string ReadPassword()
{
Stack<string> passbits = new Stack<string>();
//keep reading
for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true))
{
if (cki.Key == ConsoleKey.Backspace)
{
//rollback the cursor and write a space so it looks backspaced to the user
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
Console.Write(" ");
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
passbits.Pop();
}
else
{
Console.Write("*");
passbits.Push(cki.KeyChar.ToString());
}
}
string[] pass = passbits.ToArray();
Array.Reverse(pass);
return string.Join(string.Empty, pass);
}
}
Creo que eso es todo. También tengo una implementación de las interfaces, que (en el lado del cliente) imprime el resultado en la consola, y en el servidor ejecuta "OnLoginComplete" si el nombre de usuario y la contraseña son correctos, de lo contrario ejecuta "OnLoginError". Avíseme si no funciona o si necesita ayuda para configurarlo todo.
Suponiendo que el cliente es una aplicación de WinForms, debe hacer que el manejo de la devolución de llamada sea independiente del resto de la aplicación mediante la sugerencia de Ian y delegar el trabajo que se realizará en el subproceso de la interfaz de usuario si es necesario. Por ejemplo, si el servidor desea notificar al cliente algo, como cambiar el texto de una etiqueta, puede hacer lo siguiente:
[CallbackBehavior(UseSynchronizationContext = false)]
internal class ServiceCallback : IServiceCallback
{
ChangeMainFormLabel(string text)
{
frmMain.Instance.BeginInvoke(new Action()(() => frmMain.Instance.lblSomething.Text = text));
}
}
(La Instance
es una propiedad estática que devuelve la instancia única de frmMain
y lblSomething
es una Label
que el servidor desea cambiar.) Este método regresará de inmediato y liberará al servidor de esperar la interfaz de usuario del cliente, y la interfaz de usuario se actualizará como Tan pronto como sea libre de hacerlo. Y lo mejor de todo, no hay puntos muertos ya que nadie está esperando a nadie.