usar solo puede operador method example create await async c# memory-leaks async-await restsharp c#-5.0

solo - httpclient c# async



¿Por qué mi memoria async/await con CancelTokenSource pierde? (2)

Tengo una aplicación .NET (C #) que hace un uso extensivo de async / await. Siento que tengo mi cabeza alrededor de async / await, pero estoy tratando de usar una biblioteca (RestSharp) que tiene un modelo de programación más antiguo (o tal vez debería decir diferente) que usa devoluciones de llamada para operaciones asíncronas.

La clase RestClient de RestClient tiene un método ExecuteAsync que toma un parámetro de devolución de llamada, y quería poder poner una envoltura alrededor de lo que me permitiría await toda la operación. El método ExecuteAsync se parece a esto:

public RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action<IRestResponse> callback);

Pensé que lo tenía todo funcionando bien. Utilicé TaskCompletionSource para ajustar la llamada ExecuteAsync en algo que podría esperar, de la siguiente manera:

public static async Task<T> ExecuteRequestAsync<T>(RestRequest request, CancellationToken cancellationToken) where T : new() { var response = await ExecuteTaskAsync(request, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(response.Content); } private static async Task<IRestResponse> ExecuteTaskAsync(RestRequest request, CancellationToken cancellationToken) { var taskCompletionSource = new TaskCompletionSource<IRestResponse>(); var asyncHandle = _restClient.ExecuteAsync(request, r => { taskCompletionSource.SetResult(r); }); cancellationToken.Register(() => asyncHandle.Abort()); return await taskCompletionSource.Task; }

Esto ha funcionado bien para la mayoría de mi aplicación.

Sin embargo, tengo una parte de la aplicación que hace cientos de llamadas a mi ExecuteRequestAsync como parte de una sola operación, y esa operación muestra un cuadro de diálogo de progreso con un botón de cancelación. Verá que en el código anterior, estoy pasando un CancellationToken de ExecuteRequestAsync a ExecuteRequestAsync ; para esta operación de ejecución prolongada, el token se asocia con una CancellationTokenSource " CancellationTokenSource " que pertenece al cuadro de diálogo, cuyo método de Cancel se llama si el uso hace clic en el botón de cancelar. Hasta ahora todo bien (el botón de cancelación funciona).

Mi problema es que el uso de memoria de mi aplicación se dispara durante la aplicación de larga duración, en la medida en que se queda sin memoria antes de que se complete la operación.

He ejecutado un generador de perfiles de memoria en él y descubrí que todavía tengo muchos objetos RestResponse en la memoria, incluso después de la recolección de basura. (Ellos a su vez tienen enormes cantidades de datos, porque estoy enviando archivos de varios megabytes a través del cable).

De acuerdo con el generador de perfiles, los objetos RestResponse se mantienen vivos porque son referidos por TaskCompletionSource (a través de la Task ), que a su vez se mantiene vivo porque se hace referencia desde el CancellationTokenSource , a través de su lista de devoluciones de llamada registradas.

De todo esto, deduzco que registrar la devolución de llamada de cancelación para cada solicitud significa que todo el gráfico de objetos que está asociado con todas esas solicitudes perdurará hasta que se complete la operación completa. No es de extrañar que se quede sin memoria :-)

Así que supongo que mi pregunta no es tanto "por qué se filtra", sino "cómo lo detengo". No puedo anular el registro de la devolución de llamada, ¿qué puedo hacer?


No puedo anular el registro de la devolución de llamada

En realidad, usted puede. El valor de retorno de Register() es :

La instancia de CancellationTokenRegistration Registro que se puede usar para anular el registro de la devolución de llamada.

Para anular el registro de la devolución de llamada, llame a Dispose() en el valor devuelto.

En tu caso, podrías hacerlo así:

private static async Task<IRestResponse> ExecuteTaskAsync( RestRequest request, CancellationToken cancellationToken) { var taskCompletionSource = new TaskCompletionSource<IRestResponse>(); var asyncHandle = _restClient.ExecuteAsync( request, r => taskCompletionSource.SetResult(r)); using (cancellationToken.Register(() => asyncHandle.Abort())) { return await taskCompletionSource.Task; } }


Debe mantener el Registro de cancelación de cancelación devuelto por la llamada Registrar (). Al deshacerse de este registro cancelado cancela la devolución de llamada.