Ejecución asincrónica (método de notificación)
ODBC permite la ejecución asincrónica de operaciones de conexión y instrucciones. Un subproceso de aplicación puede llamar a una función de ODBC en modo asincrónico y la función puede devolver una respuesta antes de que se complete la operación, lo que permite que el subproceso de la aplicación realice otras tareas. En el SDK para Windows 7, para las operaciones de conexión o instrucciones asincrónicas, una aplicación determinó que la operación asincrónica se completó mediante el método de sondeo. Para obtener más información, consulte Ejecución asincrónica (método de sondeo). A partir del SDK para Windows 8, puedes determinar si una operación asincrónica se ha completado mediante el método de notificación.
En el método de sondeo, las aplicaciones deben llamar a la función asincrónica cada vez que quieran el estado de la operación. El método de notificación es similar a la devolución de llamada y espera en ADO.NET. ODBC, sin embargo, usa eventos de Win32 como objeto de notificación.
La biblioteca de cursores de ODBC y la notificación asincrónica de ODBC no se pueden usar al mismo tiempo. Al establecer ambos atributos, se devolverá un error con SQLSTATE S1119 (la biblioteca de cursores y la notificación asincrónica no se pueden habilitar al mismo tiempo).
Consulte Notificación de finalización de asincrónica (función) para obtener información sobre los desarrolladores de controladores.
Nota:
El método de notificación no es compatible con la biblioteca de cursores. Una aplicación recibirá un mensaje de error si intenta habilitar la biblioteca de cursores a través de SQLSetConnectAttr cuando está habilitado el método de notificación.
Información general
Cuando se llama a una función de ODBC en modo asincrónico, el control se devuelve a la aplicación que realiza la llamada inmediatamente con el código de retorno SQL_STILL_EXECUTING. La aplicación debe sondear repetidamente la función hasta que devuelva una respuesta distinta de SQL_STILL_EXECUTING. El bucle de sondeo aumenta el uso de la CPU, lo que provoca un rendimiento deficiente en muchos escenarios asincrónicos.
Cada vez que se usa el modelo de notificación, el modelo de sondeo se deshabilita. Las aplicaciones no deben volver a llamar a la función original. Llame a la función SQLCompleteAsync para completar la operación asincrónica. Si una aplicación vuelve a llamar a la función original antes de que se complete la operación asincrónica, la llamada devolverá SQL_ERROR con SQLSTATE IM017 (el sondeo está deshabilitado en el modo de notificación asincrónica).
Al usar el modelo de notificación, la aplicación puede llamar a SQLCancel o SQLCancelHandle para cancelar una instrucción o una operación de conexión. Si la solicitud de cancelación se realiza correctamente, ODBC devolverá SQL_SUCCESS. Este mensaje no indica que la función se haya cancelado realmente; indica que la solicitud de cancelación se ha procesado. El hecho de que la función se cancele realmente depende del controlador y del origen de datos. Cuando se cancela una operación, el Administrador de controladores seguirá señalizando el evento. El Administrador de controladores devuelve SQL_ERROR en el búfer del código de retorno y el estado es SQLSTATE HY008 (operación cancelada) para indicar que se ha cancelado correctamente. Si la función completó su procesamiento normal, el Administrador de controladores devuelve SQL_SUCCESS o SQL_SUCCESS_WITH_INFO.
Comportamiento de nivel inferior
La versión del Administrador de controladores ODBC que admite completamente esta notificación es ODBC 3.81.
Versión de ODBC de la aplicación | Versión del Administrador de controladores | Versión del controlador | Comportamiento |
---|---|---|---|
Nueva aplicación de cualquier versión de ODBC | ODBC 3.81 | Controlador ODBC 3.80 | La aplicación puede usar esta característica si el controlador la admite; de lo contrario, se producirá un error en el Administrador de controladores. |
Nueva aplicación de cualquier versión de ODBC | ODBC 3.81 | Controlador anterior a ODBC 3.80 | El Administrador de controladores producirá un error si el controlador no admite esta característica. |
Nueva aplicación de cualquier versión de ODBC | Anterior a ODBC 3.81 | Any | Cuando la aplicación usa esta característica, un Administrador de controladores antiguo considerará los nuevos atributos como atributos específicos del controlador y el controlador debería producir un error. Un nuevo Administrador de controladores no pasará estos atributos al controlador. |
Antes de usar esta característica, una aplicación debe comprobar la versión del Administrador de controladores. De lo contrario, si un controlador mal escrito no genera errores y la versión del Administrador de controladores es anterior a ODBC 3.81, el comportamiento es indefinido.
Casos de uso:
En esta sección se muestran los casos de uso para la ejecución asincrónica y el mecanismo de sondeo.
Integración de datos de varios orígenes de ODBC
Una aplicación de integración de datos captura de forma asincrónica los datos de varios orígenes de datos. Algunos de los datos proceden de orígenes de datos remotos y algunos proceden de archivos locales. La aplicación no puede continuar hasta que se completen las operaciones asincrónicas.
En lugar de sondear repetidamente una operación para determinar si se ha completado, la aplicación puede crear un objeto de evento y asociarlo a un identificador de conexión de ODBC o a un identificador de instrucción de ODBC. A continuación, la aplicación llama a las API de sincronización del sistema operativo para esperar un objeto de evento o muchos objetos de evento (eventos de ODBC y otros eventos de Windows). ODBC indicará al objeto de evento cuando se complete la operación asincrónica de ODBC correspondiente.
En Windows, se usarán objetos de evento de Win32, lo que proporcionará al usuario un modelo de programación unificado. Los Administradores de controladores en otras plataformas pueden usar la implementación de objetos de evento específica de esas plataformas.
En el ejemplo de código siguiente se muestra el uso de la notificación asincrónica de conexión e instrucción:
// This function opens NUMBER_OPERATIONS connections and executes one query on statement of each connection.
// Asynchronous Notification is used
#define NUMBER_OPERATIONS 5
int AsyncNotificationSample(void)
{
RETCODE rc;
SQLHENV hEnv = NULL;
SQLHDBC arhDbc[NUMBER_OPERATIONS] = {NULL};
SQLHSTMT arhStmt[NUMBER_OPERATIONS] = {NULL};
HANDLE arhDBCEvent[NUMBER_OPERATIONS] = {NULL};
RETCODE arrcDBC[NUMBER_OPERATIONS] = {0};
HANDLE arhSTMTEvent[NUMBER_OPERATIONS] = {NULL};
RETCODE arrcSTMT[NUMBER_OPERATIONS] = {0};
rc = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &hEnv);
if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
rc = SQLSetEnvAttr(hEnv,
SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3_80,
SQL_IS_INTEGER);
if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
// Connection operations begin here
// Alloc NUMBER_OPERATIONS connection handles
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
rc = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &arhDbc[i]);
if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
}
// Enable DBC Async on all connection handles
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
rc= SQLSetConnectAttr(arhDbc[i], SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_ON, SQL_IS_INTEGER);
if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
}
// Application must create event objects
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
arhDBCEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, initial state is not-signaled
if (!arhDBCEvent[i]) goto Cleanup;
}
// Enable notification on all connection handles
// Event
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
rc= SQLSetConnectAttr(arhDbc[i], SQL_ATTR_ASYNC_DBC_EVENT, arhDBCEvent[i], SQL_IS_POINTER);
if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
}
// Initiate connect establishing
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
SQLDriverConnect(arhDbc[i], NULL, (SQLTCHAR*)TEXT("Driver={ODBC Driver 11 for SQL Server};SERVER=dp-srv-sql2k;DATABASE=pubs;UID=sa;PWD=XYZ;"), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
}
// Can do some other staff before calling WaitForMultipleObjects
WaitForMultipleObjects(NUMBER_OPERATIONS, arhDBCEvent, TRUE, INFINITE); // Wait All
// Complete connect API calls
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
SQLCompleteAsync(SQL_HANDLE_DBC, arhDbc[i], & arrcDBC[i]);
}
BOOL fFail = FALSE; // Whether some connection openning fails.
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if ( !SQL_SUCCEEDED(arrcDBC[i]) )
fFail = TRUE;
}
// If some SQLDriverConnect() fail, clean up.
if (fFail)
{
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if (SQL_SUCCEEDED(arrcDBC[i]) )
{
SQLDisconnect(arhDbc[i]); // This is also async
}
else
{
SetEvent(arhDBCEvent[i]); // Previous SQLDriverConnect() failed. No need to call SQLDisconnect().
}
}
WaitForMultipleObjects(NUMBER_OPERATIONS, arhDBCEvent, TRUE, INFINITE);
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if (SQL_SUCCEEDED(arrcDBC[i]) )
{
SQLCompleteAsync(SQL_HANDLE_DBC, arhDbc[i], &arrcDBC[i]);; // To Complete
}
}
goto Cleanup;
}
// Statement Operations begin here
// Alloc statement handle
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
rc = SQLAllocHandle(SQL_HANDLE_STMT, arhDbc[i], &arhStmt[i]);
if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
}
// Enable STMT Async on all statement handles
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
rc = SQLSetStmtAttr(arhStmt[i], SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_ON, SQL_IS_INTEGER);
if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
}
// Create event objects
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
arhSTMTEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, initial state is not-signaled
if (!arhSTMTEvent[i]) goto Cleanup;
}
// Enable notification on all statement handles
// Event
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
rc= SQLSetStmtAttr(arhStmt[i], SQL_ATTR_ASYNC_STMT_EVENT, arhSTMTEvent[i], SQL_IS_POINTER);
if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
}
// Initiate SQLExecDirect() calls
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
SQLExecDirect(arhStmt[i], (SQLTCHAR*)TEXT("select au_lname, au_fname from authors"), SQL_NTS);
}
// Can do some other staff before calling WaitForMultipleObjects
WaitForMultipleObjects(NUMBER_OPERATIONS, arhSTMTEvent, TRUE, INFINITE); // Wait All
// Now, call SQLCompleteAsync to complete the operation and get return code
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
SQLCompleteAsync(SQL_HANDLE_STMT, arhStmt[i], &arrcSTMT[i]);
}
// Check return values
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if ( !SQL_SUCCEEDED(arrcSTMT[i]) ) goto Cleanup;
}
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
//Do some binding jobs here, set SQL_ATTR_ROW_ARRAY_SIZE
}
// Now, initiate fetching
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
SQLFetch(arhStmt[i]);
}
// Can do some other staff before calling WaitForMultipleObjects
WaitForMultipleObjects(NUMBER_OPERATIONS, arhSTMTEvent, TRUE, INFINITE);
// Now, to complete the operations and get return code
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
SQLCompleteAsync(SQL_HANDLE_STMT, arhStmt[i], &arrcSTMT[i]);
}
// Check return code
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if ( !SQL_SUCCEEDED(arrcSTMT[i]) ) goto Cleanup;
}
// USE fetched data here!!
Cleanup:
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if (arhStmt[NUMBER_OPERATIONS])
{
SQLFreeHandle(SQL_HANDLE_STMT, arhStmt[i]);
arhStmt[i] = NULL;
}
}
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if (arhSTMTEvent[i])
{
CloseHandle(arhSTMTEvent[i]);
arhSTMTEvent[i] = NULL;
}
}
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if (arhDbc[i])
{
SQLFreeHandle(SQL_HANDLE_DBC, arhDbc[i]);
arhDbc[i] = NULL;
}
}
for (int i=0; i<NUMBER_OPERATIONS; i++)
{
if (arhDBCEvent[i])
{
CloseHandle(arhDBCEvent[i]);
arhDBCEvent[i] = NULL;
}
}
if (hEnv)
{
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
hEnv = NULL;
}
return 0;
}
Determinar si un controlador admite las notificaciones asincrónicas
Una aplicación de ODBC puede determinar si un controlador ODBC admite notificaciones asincrónicas llamando a SQLGetInfo. En consecuencia, el Administrador de controladores ODBC llamará a SQLGetInfo del controlador con SQL_ASYNC_NOTIFICATION.
SQLUINTEGER InfoValue;
SQLLEN cbInfoLength;
SQLRETURN retcode;
retcode = SQLGetInfo (hDbc,
SQL_ASYNC_NOTIFICATION,
&InfoValue,
sizeof(InfoValue),
NULL);
if (SQL_SUCCEEDED(retcode))
{
if (SQL_ASYNC_NOTIFICATION_CAPABLE == InfoValue)
{
// The driver supports asynchronous notification
}
else if (SQL_ASYNC_NOTIFICATION_NOT_CAPABLE == InfoValue)
{
// The driver does not support asynchronous notification
}
}
Asociación de un identificador de eventos de Win32 con un identificador ODBC
Las aplicaciones son responsables de crear objetos de eventos de Win32 mediante las funciones de Win32 correspondientes. Una aplicación puede asociar un identificador de eventos de Win32 a un identificador de conexión ODBC o un identificador de instrucción ODBC.
Los atributos de conexión SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE y SQL_ATTR_ASYNC_DBC_EVENT determinan si ODBC se ejecuta en modo asincrónico y si ODBC habilita el modo de notificación para un identificador de conexión. Los atributos de instrucción SQL_ATTR_ASYNC_ENABLE y SQL_ATTR_ASYNC_STMT_EVENT determinan si ODBC se ejecuta en modo asincrónico y si ODBC habilita el modo de notificación para un identificador de instrucción.
SQL_ATTR_ASYNC_ENABLE o SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE | SQL_ATTR_ASYNC_STMT_EVENT o SQL_ATTR_ASYNC_DBC_EVENT | Modo |
---|---|---|
Habilitar | no es NULL | Notificación asincrónica |
Habilitar | nulo | Sondeo asincrónico |
Deshabilitar | cualquiera | Sincrónico |
Una aplicación puede deshabilitar temporalmente el modo de operación asincrónica. ODBC omite los valores de SQL_ATTR_ASYNC_DBC_EVENT si la operación asincrónica de nivel de conexión está deshabilitada. ODBC omite los valores de SQL_ATTR_ASYNC_STMT_EVENT si la operación asincrónica del nivel de instrucción está deshabilitada.
Llamada sincrónica de SQLSetStmtAttr y SQLSetConnectAttr
SQLSetConnectAttr admite las operaciones asincrónicas, pero la invocación de SQLSetConnectAttr para establecer SQL_ATTR_ASYNC_DBC_EVENT siempre es sincrónica.
SQLSetStmtAttr no admite la ejecución asincrónica.
Escenario de generación de errores
Cuando se llama a SQLSetConnectAttr antes de realizar una conexión, el Administrador de controladores no puede determinar qué controlador usar. Por lo tanto, el Administrador de controladores devuelve el éxito de SQLSetConnectAttr, pero es posible que el atributo no esté listo para establecerse en el controlador. El Administrador de controladores establecerá estos atributos cuando la aplicación llame a una función de conexión. El Administrador de controladores puede producir errores porque el controlador no admite las operaciones asincrónicas.
Herencia de atributos de conexión
Normalmente, las instrucciones de una conexión heredarán los atributos de conexión. Sin embargo, el atributo SQL_ATTR_ASYNC_DBC_EVENT no se puede heredar y solo afecta a las operaciones de conexión.
Para asociar un identificador de evento a un identificador de conexión ODBC, una aplicación de ODBC llama a la API de ODBC SQLSetConnectAttr y especifica SQL_ATTR_ASYNC_DBC_EVENT como atributo y el identificador de evento como valor de atributo. El nuevo atributo de ODBC SQL_ATTR_ASYNC_DBC_EVENT es de tipo SQL_IS_POINTER.
HANDLE hEvent;
hEvent = CreateEvent(
NULL, // default security attributes
FALSE, // auto-reset event
FALSE, // initial state is non-signaled
NULL // no name
);
Normalmente, las aplicaciones crean objetos de evento de restablecimiento automático. ODBC no restablecerá el objeto de evento. Las aplicaciones deben asegurarse de que el objeto no esté en estado señalado antes de llamar a cualquier función de ODBC asincrónica.
SQLRETURN retcode;
retcode = SQLSetConnectAttr ( hDBC,
SQL_ATTR_ASYNC_DBC_EVENT, // Attribute name
(SQLPOINTER) hEvent, // Win32 Event handle
SQL_IS_POINTER); // Length Indicator
SQL_ATTR_ASYNC_DBC_EVENT es un atributo solo del Administrador de controladores que no se establecerá en el controlador.
El valor predeterminado de SQL_ATTR_ASYNC_DBC_EVENT es NULL. Si el controlador no admite las notificaciones asincrónicas, obtener o establecer SQL_ATTR_ASYNC_DBC_EVENT devolverá SQL_ERROR con SQLSTATE HY092 (identificador de atributo u opción no válido).
Si el último valor de SQL_ATTR_ASYNC_DBC_EVENT establecido en un identificador de conexión ODBC no es NULL y la aplicación habilitó el modo asincrónico estableciendo el atributo SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE con SQL_ASYNC_DBC_ENABLE_ON, llamar a cualquier función de conexión de ODBC que admita el modo asincrónico recibirá una notificación de finalización. Si el último valor de SQL_ATTR_ASYNC_DBC_EVENT establecido en un identificador de conexión ODBC es NULL, ODBC no enviará ninguna notificación a la aplicación, independientemente de si el modo asincrónico está habilitado.
Una aplicación puede establecer SQL_ATTR_ASYNC_DBC_EVENT antes o después de establecer el atributo SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE.
Las aplicaciones pueden establecer el atributo SQL_ATTR_ASYNC_DBC_EVENT en un identificador de conexión ODBC antes de llamar a una función de conexión (SQLConnect, SQLBrowseConnect o SQLDriverConnect). Dado que el Administrador de controladores ODBC no sabe qué controlador ODBC usará la aplicación, devolverá SQL_SUCCESS. Cuando la aplicación llama a una función de conexión, el Administrador de controladores ODBC comprobará si el controlador admite las notificaciones asincrónicas. Si el controlador no admite las notificaciones asincrónicas, el Administrador de controladores ODBC devolverá SQL_ERROR con SQLSTATE S1_118 (el controlador no admite notificaciones asincrónicas). Si el controlador admite las notificaciones asincrónicas, el Administrador de controladores ODBC llamará al controlador y establecerá los atributos correspondientes SQL_ATTR_ASYNC_DBC_NOTIFICATION_CALLBACK y SQL_ATTR_ASYNC_DBC_NOTIFICATION_CONTEXT.
De forma similar, una aplicación llama a SQLSetStmtAttr en un identificador de instrucción ODBC y especifica el atributo SQL_ATTR_ASYNC_STMT_EVENT para habilitar o deshabilitar la notificación asincrónica del nivel de instrucción. Dado que siempre se llama a una función de instrucción después de establecer la conexión, SQLSetStmtAttr devolverá SQL_ERROR con SQLSTATE S1_118 (el controlador no admite las notificaciones asincrónicas) inmediatamente si el controlador correspondiente no admite las operaciones asincrónicas o el controlador admite las operaciones asincrónicas, pero no las notificaciones asincrónicas.
SQLRETURN retcode;
retcode = SQLSetStmtAttr ( hSTMT,
SQL_ATTR_ASYNC_STMT_EVENT, // Attribute name
(SQLPOINTER) hEvent, // Win32 Event handle
SQL_IS_POINTER); // length Indicator
SQL_ATTR_ASYNC_STMT_EVENT, que se puede establecer en NULL, es un atributo solo del Administrador de controladores que no se establecerá en el controlador.
El valor predeterminado de SQL_ATTR_ASYNC_STMT_EVENT es NULL. Si el controlador no admite las notificaciones asincrónicas, obtener o establecer el atributo SQL_ATTR_ASYNC_ STMT_EVENT devolverá SQL_ERROR con SQLSTATE HY092 (identificador de atributo u opción no válido).
Una aplicación no debe asociar el mismo identificador de eventos con más de un identificador ODBC. De lo contrario, se perderá la notificación si se completan dos invocaciones asincrónicas de función de ODBC en dos identificadores que comparten el mismo identificador de eventos. Para evitar que un identificador de instrucción herede el mismo identificador de evento del identificador de conexión, ODBC devuelve SQL_ERROR con SQLSTATE IM016 (no se puede establecer el atributo de instrucción en el identificador de conexión) si una aplicación establece SQL_ATTR_ASYNC_STMT_EVENT en un identificador de conexión.
Llamar a funciones de ODBC asincrónicas
Después de habilitar la notificación asincrónica e iniciar una operación asincrónica, la aplicación puede llamar a cualquier función de ODBC. Si la función pertenece al conjunto de funciones que admiten las operaciones asincrónicas, la aplicación recibirá una notificación de finalización cuando se complete la operación, independientemente de si la función produjo un error o se realizó correctamente. La única excepción es que la aplicación llama a una función de ODBC con un identificador de instrucción o conexión no válidos. En este caso, ODBC no obtendrá el identificador de eventos y lo establecerá en el estado señalado.
La aplicación debe asegurarse de que el objeto de evento asociado esté en un estado no señalizado antes de iniciar una operación asincrónica en el identificador de ODBC correspondiente. ODBC no restablecerá el objeto de evento.
Obtener notificaciones de ODBC
Un subproceso de aplicación puede llamar a WaitForSingleObject para esperar un identificador de evento o llamar a WaitForMultipleObjects para esperar una matriz de identificadores de eventos y estar suspendido hasta que se señalen uno o todos los objetos de evento o haya transcurrido el intervalo de tiempo de espera.
DWORD dwStatus = WaitForSingleObject(
hEvent, // The event associated with the ODBC handle
5000 // timeout is 5000 millisecond
);
If (dwStatus == WAIT_TIMEOUT)
{
// time-out interval elapsed before all the events are signaled.
}
Else
{
// Call the corresponding Asynchronous ODBC API to complete all processing and retrieve the return code.
}
Comentaris
https://aka.ms/ContentUserFeedback.
Properament: al llarg del 2024 eliminarem gradualment GitHub Issues com a mecanisme de retroalimentació del contingut i el substituirem per un nou sistema de retroalimentació. Per obtenir més informació, consulteu:Envieu i consulteu els comentaris de