Alternative solution: wait for the handles of the task and the manual reset event
I was having memory leaks when using Task.WaitAny()
with a Task
(returned by SqlConnection.OpenAsync()') and a Manual Reset Event received as parameter and wrapped in a Task
with AsTask()
. These object were not being disposed: TaskCompletionSource<Object>, Task<Object>, StandardTaskContinuation, RegisteredWaitHandle, RegisteredWaithandleSafe, ContinuationResultTaskFromresultTask<Object,bool>, _ThreadPoolWaitOrTimerCallback
).
This is real production code, used in a Windows service, of a function that tries to open a connection to a db in a loop until the connection is opened, or the operation fails, or the ManualResetEvent _finishRequest
, received as parameter in the function containing this code, is signaled by code in any other thread.
To avoid the leak, I decided to do it the other way round: wait for the handles of the _finishRequest
and the Task
returned by OpenAsync()
:
Task asyncOpening = sqlConnection.OpenAsync();
// Wait for the async open to finish, or until _finishRequest is signaled
var waitHandles = new WaitHandle[]
{
// index 0 in array: extract the AsyncWaitHandle from the Task
((IAsyncResult)asyncOpening).AsyncWaitHandle,
// index 1:
_finishRequest
};
// Check if finish was requested (index of signaled handle in the array = 1)
int whichFinished = WaitHandle.WaitAny(waitHandles);
finishRequested = whichFinished == 1;
// If so, break the loop to exit immediately
if (finishRequested)
break;
// If not, check if OpenAsync finished with error (it's a Task)
if (asyncOpening.IsFaulted)
{
// Extract the exception from the task, and throw it
// NOTE: adapt it to your case. In mine, I'm interested in the inner exception,
// but you can check the exception itself, for example to see if it was a timeout,
// if you specified it in the call to the async function that returns the Task
var ex = asyncOpening?.Exception?.InnerExceptions?[0];
if (ex != null) throw ex;
}
else
{
Log.Verbose("Connection to database {Database} on server {Server}", database, server);
break;
}
If you also need the timeout, you can include it in the call to OpenAsync
, or you asyn function, and then check if the result of the async operation was cancelled because of the timeout: check the status of the Task when finished, as you can see in the NOTE in the code comment.