2010-06-14 10 views
8

(Asumiendo un método WCF llamado "MyFunction")¿Cuál es la mejor manera de cancelar una solicitud WCF asíncrona?

Actualmente, para apoyar cancelar una solicitud de WCF, estoy usando los métodos BeginMyFunction/EndMyFunction generados por svcutil (y gastos de una bandera isCanceled cuando el envío de los resultados de la principal hilo). Me gustaría usar el método MyFunctionAsync (y conectarlo al evento MyFunctionAsyncCompleted en su lugar) para llamadas asíncronas en lugar de Begin/End.

¿Cuál es la mejor forma de gestionar las solicitudes WCF canceladas si se utiliza MyFunctionAsyncCompleted y garantizar que el evento no se active en una página que ya no se carga (es decir, navegación de página dentro de un marco).

Gracias!

EDIT:

He decidido que quiero crear mi objetivo WcfClient sobre una base por llamada (a diferencia de per-WPF-página o por aplicación), así que aquí es lo que yo' ve venir con:

public void StartValidation(){ 
    WcfClient wcf = new WcfClient(); 
    wcf.IsValidCompleted += new EventHandler<IsValidCompletedEventArgs>(wcf_IsValidCompleted); 
    //pass the WcfClient object as the userState parameter so it can be closed later 
    wcf.IsValidAsync(TextBox.Text, wcf); 
} 

void wcf_IsValidCompleted(object sender, IsValidCompletedEventArgs e) { 
    if(!m_IsCanceled){ 
     //Update the UI 
     //m_IsCanceled is set to true when the page unload event is fired 
    } 
    //Close the connection 
    if (e.UserState is WcfClient) { 
     ((WcfClient)e.UserState).Close(); 
    } 
} 

Me resulta difícil averiguar cuál es la forma recomendada para lograr lo que acabo de implementar. ¿Está bien tal como está, o hay problemas/casos extremos de los que debo preocuparme? ¿Cuál es el estándar de oro cuando se trata de cancelar correctamente una llamada WCF?

+0

De esta manera, usted no está realmente cancelando la llamada (es decir, aún se está ejecutando hasta su finalización). Solo está comprobando si debe ignorar los resultados, ¿verdad? – GuyBehindtheGuy

+0

Muy cierto. Esto ahora garantiza que al menos mi UI no se actualice después de que la página se descargue. Estoy investigando qué secuencia de llamadas debo hacer en el evento de descarga (es decir, Cerrar, Abortar, etc.) para eludir esta técnica de "marcado" que estoy usando actualmente. – Pwninstein

+0

Llamar 'Abortar' al descargar no siempre cancela la solicitud (de hecho, la solicitud casi siempre se completa, Fiddler lo demuestra). Agregué un retraso de 5 segundos en el lado del servidor para darme tiempo a abortar la llamada. Esto no es lo que esperaría. – Pwninstein

Respuesta

6

No hay forma de cancelar la solicitud asincrónica a menos que cree manualmente las funciones asincrónicas. Teniendo en cuenta que estás generando automáticamente tus llamadas WCF que lo harían más de una tarea. Incluso entonces, como dijiste, la llamada no cancela, seguirá ejecutándose. Si aún desea tener una cancelación, solo tiene que asegurarse de que el cliente/IU ignore los resultados de la llamada.

2

Cancelar una solicitud no es realmente práctico en el sentido general simplemente debido a la naturaleza asíncrona de la solicitud de la que está hablando. Se pueden realizar soluciones temporales específicas. Por ejemplo, agregando una nueva señal de cancelación a su protocolo que establece un estado compartido que la tarea principal de larga ejecución comprueba periódicamente para verificar que debe continuar ejecutándose.

De cualquier manera, una vez más debido a la naturaleza asíncrona de la solicitud, creo que sigue siendo responsabilidad del cliente ignorar los resultados de una solicitud que se ha cancelado. No es posible garantizar que el cliente no reciba una respuesta de una solicitud que simplemente se cancela simplemente porque existe una competencia entre el cliente y el servidor. Es posible agregar una capa adicional que supervisa si se ha cancelado algo y sabe que descarta la respuesta de una solicitud, pero eso no impide que el resultado se transmita al cliente.

1

Si lo entiendo correctamente, está intentando cancelar correctamente una llamada pendiente a un servicio WCF. Desea utilizar el evento MyFunctionCompleted porque se maneja en el subproceso UI.

Lo que probablemente deba hacer es llamar al método Abort en el WcfClient (debe mantener una referencia). Esto limpiará los recursos en el lado del cliente. El servidor aún terminará la solicitud, pero el cliente ya no esperará más. Poco después se desencadena el evento MyFunctionCompleted. Al marcar client.State sabrá si la llamada tuvo éxito, falló o fue cancelada.

que aquí hay una pequeña aplicación de prueba que tiene un botón Enviar, un botón Abortar y un cuadro de texto para los resultados:

public partial class MainForm : Form 
{ 
    public MainForm() 
    { 
     InitializeComponent(); 
    } 

    private SomeServiceClient m_client; 

    private void buttonSend_Click(object sender, EventArgs e) 
    { 
     m_client = new SomeServiceClient(); 
     m_client.MyFunctionCompleted += new EventHandler<MyFunctionCompletedEventArgs>(client_MyFunctionCompleted); 
     m_client.MyFunctionAsync(4000, m_client); 
    } 

    private void buttonAbout_Click(object sender, EventArgs e) 
    { 
     if(m_client != null) 
      m_client.Abort(); 
    } 

    void client_MyFunctionCompleted(object sender, MyFunctionCompletedEventArgs e) 
    { 

     var client = e.UserState as SomeServiceClient; 
     if (client != null) 
     { 
      if (client.State == System.ServiceModel.CommunicationState.Opened) 
      { 
       textBox.Text += string.Format("Result: {0}\r\n", e.Result); 
       client.Close(); 
       return; 
      } 
      else if (client.State == System.ServiceModel.CommunicationState.Faulted) 
      { 
       textBox.Text += string.Format("Error: {0}\r\n", e.Error.Message); 
      } 
      client.Abort(); 
     } 
    } 
} 

No hay manejo de excepciones y la limpieza ... No sé si es la forma recomendada, pero creo que llamar al aborto es el camino correcto. Debe manejar las diferentes situaciones de error de cualquier manera.

4

.Net 4,5

  1. Cada WCF aplicación llamada crea un CancellationTokenSource y lo almacena en un lugar de fácil acceso a todos los servicios de WCF. Es decir. en entornos de servidor único puede estar en una memoria caché.
  2. CancellationTokenSource.Token se pasa a todas las llamadas de método iniciadas por el método WCF, incluidas las llamadas a las bases de datos y las llamadas de red cuando corresponda.
  3. El método WCF puede tener un identificador único como parámetro o puede devolver un identificador único asociado a CancellationTokenSource.
  4. Cuando el cliente necesita cancelar la operación llama a un método WCF y le pasa el identificador único de la llamada anterior.
  5. CancelaciónTokenSource se recupera utilizando el identificador único y se invoca su método Cancelar. Si el token de cancelación se maneja correctamente, la operación iniciada por una llamada anterior se cancelará pronto y puede devolver OperationCanceledException o un CancelFault o algún otro error que indique que el cliente que llamó fue cancelado.
  6. Cuando las llamadas del cliente se cancelan en el paso 4 al mismo tiempo, puede cancelar la llamada WCF original. Sin embargo, si los clientes manejan adecuadamente OperationCanceledException o CancelFault esto no es necesario. OperationCanceledException puede burbujear hasta una llamada ajax si la llamada original se inició desde una página web.

Algo similar se puede implementar en los viejos frameworks de .Net donde CancelToken aún no está disponible.

+2

Gracias por la respuesta. Algunas referencias o muestras de código serían geniales. – Kurren

10

La manera más fácil que conozco para hacer esto con un cliente WCF y el patrón asincrónico basado tarea es registrar una acción Abortar con la cancelación símbolo

private async Task CallOrAbortMyServiceAsync(CancellationToken cancellation) 
{ 
    var client = new SomeServiceClient(); 
    cancellation.Register(() => client.Abort()); 
    try 
    { 
     await client.CallMyServiceAsync(); 
    } catch (CommunicationException) { 
      // This will be called when you are cancelled, or some other fault. 
    } 
} 
+2

+1 ¡Buena implementación! Simplemente recomendaría eliminar el resultado de 'Cancellation.Register()', de lo contrario, el registro podría tener fugas. – BlueStrat

+1

Honestamente, esa debería ser la respuesta aceptada. – Fabien

Cuestiones relacionadas