Question

I am implementing code from the accepted answer on this question. Now I'm having a problem looking like threads are being left open: the application never finishes.

I think this may be caused by the bottom one of the two occurences of ThreadPool.RegisterWaitForSingleObject below. The first one listens for the WebRequest to finish or for the timeout to occur. The second one listens to a Cancellation being requested. Now I'm wondering: does the second one ever finish if no cancellation is requested or will it keep waiting forever?

public static Task<WebResponse> GetResponseAsync(this WebRequest request, CancellationToken token)
{
    if (request == null)
        throw new ArgumentNullException("request");

    bool timeout = false;
    TaskCompletionSource<WebResponse> completionSource = new TaskCompletionSource<WebResponse>();

    AsyncCallback completedCallback =
        result =>
        {
            try
            {
                completionSource.TrySetResult(request.EndGetResponse(result));
            }
            catch (WebException ex)
            {
                if (timeout)
                    completionSource.TrySetException(new WebException("No response was received during the time-out period for a request.", WebExceptionStatus.Timeout));
                else if (token.IsCancellationRequested)
                    completionSource.TrySetCanceled();
                else
                    completionSource.TrySetException(ex);
            }
            catch (Exception ex)
            {
                completionSource.TrySetException(ex);
            }
        };

    IAsyncResult asyncResult = request.BeginGetResponse(completedCallback, null);
    if (!asyncResult.IsCompleted)
    {
        if (request.Timeout != Timeout.Infinite)
        {
            WaitOrTimerCallback timedOutCallback =
                (object state, bool timedOut) =>
                {
                    if (timedOut)
                    {
                        timeout = true;
                        request.Abort();
                    }
                };

            ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, timedOutCallback, null, request.Timeout, true);
        }

        if (token != CancellationToken.None)
        {
            WaitOrTimerCallback cancelledCallback =
                (object state, bool timedOut) =>
                {
                    if (token.IsCancellationRequested)
                        request.Abort();
                };

            ThreadPool.RegisterWaitForSingleObject(token.WaitHandle, cancelledCallback, null, Timeout.Infinite, true);
        }
    }

    return completionSource.Task;
}
Was it helpful?

Solution

Yes, the thread pool will wait for the Cancellation Token's wait handle to be signaled indefinitely.

You need to save the return value of RegisterWaitForSingleObject() in order to cancel the outstanding wait operation. See ThreadPool.RegisterWaitForSingleObject Remarks section as it indicates you should always unregister waits.

The outstanding wait operation should not keep your process alive though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top