user3904868
user3904868

Reputation:

Task.IsCompleted not returning correct task state

I'm trying to capture the state of a long running task during the Form1_FormClosing event.

The long running task comprises of async/await calls using the HttpClient.

When I start the task running it runs in loop until cancelled. However, when I close the form with the task still running the task status is not as expected and showing status RanToCompletion and IsCompleted==true

I've replicated the problem using method RunLongRunningMethodTest

Task.Delay(2000); works okay but await Task.Delay(2000); replicates the same issue. I'm not sure how to get around the fact that GetUrl must also be async due to the GetAsync method.

How can I fix the code below so that Form1_FormClosing correctly reports that the task is still running if closed while it runs? I want to check the task state and cancel if still running and wait for the cancel to complete.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private static HttpClient client { get; set; }
        private Task task { get; set; }
        private CancellationTokenSource cts { get; set; }
        private bool buttonStartStopState { get; set; }

        public Form1()
        {
            InitializeComponent();
        }

        private async void RunLongRunningMethodTest(CancellationToken cancellationToken)
        {
            try
            {
                while (true)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    Debug.WriteLine(DateTime.Now);
                    Task.Delay(2000); // task.IsCompleted = false - Works okay
                    //await Task.Delay(2000); // task.IsCompleted = true - Not correct
                }
            }
            catch (OperationCanceledException)
            {
                // Just exit without logging. Operation cancelled by user.
            }
            catch (Exception ex)
            {
                // Report Error
            }
        }

        private async void RunLongRunningMethod(CancellationToken cancellationToken)
        {
            try
            {
                while (true)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    var success = await GetUrl("https://www.bbc.co.uk/");
                    Thread.Sleep(2000);
                }
            }
            catch (OperationCanceledException)
            {
                // Just exit without logging. Operation cancelled by user.
            }
            catch (Exception ex)
            {
                // Report Error
            }
        }

        private async Task<bool> GetUrl(string url)
        {
            if (client == null)
            {
                client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true, AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip });
                client.BaseAddress = new Uri("https://www.bbc.co.uk/");
                client.DefaultRequestHeaders.Add("Accept", "*/*");
                client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
                client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/62.0");
                client.DefaultRequestHeaders.Add("Host", "https://www.bbc.co.uk/");
                client.DefaultRequestHeaders.Connection.Add("Keep-Alive");
                client.DefaultRequestHeaders.Add("DNT", "1");
            }

            var response = await client.GetAsync(url);
            var contents = await response.Content.ReadAsStringAsync();

            return true;
        }

        private void buttonStartStop_Click(object sender, EventArgs e)
        {
            buttonStartStopState = !buttonStartStopState;
            if(buttonStartStopState)
            {
                cts = new CancellationTokenSource();
                task = new Task(() => RunLongRunningMethod(cts.Token));
                task.Start();
            }
            else
            {
                cts.Cancel();
                cts = null;
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {

            if (task != null)
            {
                var taskStatus = $"{task.Status} {task.IsCompleted}";
            }
        }
    }
}

Upvotes: 1

Views: 6974

Answers (1)

Camilo Terevinto
Camilo Terevinto

Reputation: 32068

The first problem is your use of async void in your long running methods. When the code reaches the await line, the control is yielded back to the parent, and since the Task does not have anything else to run, it correctly reports that it has finished.

So, let's fix that:

private async Task RunLongRunningMethodTest
private async Task RunLongRunningMethod

The second problem is the use of the Task constructor. Not only its use is discouraged, but it is also unnecessary in your code. You should be doing just this:

if (buttonStartStopState)
{
    cts = new CancellationTokenSource();
    task = RunLongRunningMethod(cts.Token); // will only compile after fixing the first problem
}

As an additional note, notice that using Task.Delay(2000); is effectively useless, since you are creating a Task that is thrown away.

Upvotes: 6

Related Questions